diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index 8548b2be..cabc67f6 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 ec719794..0edb4239 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 30740956..b6b4efa0 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 9a1a083b..ccfa668b 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 43ec9e54..466a5406 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/datums/mind.dm b/code/datums/mind.dm index f633b5cf..5dd0b34d 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -62,6 +62,9 @@ var/unconvertable = FALSE var/late_joiner = FALSE + var/last_death = 0 + + var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator var/list/learned_recipes //List of learned recipe TYPES. diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 9e8756bb..78953609 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -598,6 +598,14 @@ /atom/proc/multitool_act(mob/living/user, obj/item/I) return +///Check if the multitool has an item in it's data buffer +/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE) + if(!istype(I, /obj/item/multitool)) + if(user && !silent) + to_chat(user, "[I] has no data buffer!") + return FALSE + return TRUE + /atom/proc/screwdriver_act(mob/living/user, obj/item/I) SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 855ede0e..a70c1068 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -19,7 +19,7 @@ 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 = null //So we remember the connected clone machine. + 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 @@ -53,7 +53,10 @@ 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) @@ -74,6 +77,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 @@ -102,7 +122,7 @@ //Clonepod /obj/machinery/clonepod/examine(mob/user) - ..() + . = ..() var/mob/living/mob_occupant = occupant if(mess) to_chat(user, "It's filled with blood and viscera. You swear you can see it moving...") @@ -129,44 +149,54 @@ 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, 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(clonemind.active) //somebody is using that mind - if( ckey(clonemind.key)!=ckey ) + 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 - 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. + 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 @@ -184,15 +214,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 @@ -206,7 +237,7 @@ H.suiciding = FALSE attempting = FALSE - return TRUE + return CLONING_SUCCESS //Grow clones to maturity then kick them out. FREELOADERS /obj/machinery/clonepod/process() @@ -215,16 +246,20 @@ 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.") + mob_occupant.apply_vore_prefs() + connected_message("Clone Ejected: Loss of power.") else if(mob_occupant && (mob_occupant.loc == src)) - if((mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes. + if(mob_occupant && (mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes. connected_message("Clone Rejected: Deceased.") if(internal_radio) SPEAK("The cloning has been \ aborted due to unrecoverable tissue failure.") go_out() + log_cloning("[key_name(mob_occupant)] ejected from [src] at [AREACOORD(src)] after suiciding.") mob_occupant.apply_vore_prefs() else if(mob_occupant.cloneloss > (100 - heal_level)) @@ -272,10 +307,13 @@ BP.attach_limb(mob_occupant) go_out() + log_cloning("[key_name(mob_occupant)] completed cloning cycle in [src] at [AREACOORD(src)].") mob_occupant.apply_vore_prefs() else if (!mob_occupant || mob_occupant.loc != src) occupant = null + if (!mess && !panel_open) + icon_state = "pod_0" use_power(200) update_icon() @@ -290,6 +328,8 @@ return if(istype(W, /obj/item/multitool)) + if(!multitool_check_buffer(user, W)) + return var/obj/item/multitool/P = W if(istype(P.buffer, /obj/machinery/computer/cloning)) @@ -316,10 +356,13 @@ to_chat(user, "Error: Pod has no occupant.") return else + add_fingerprint(user) connected_message("Emergency Ejection") SPEAK("An emergency ejection of the current clone 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]") mob_occupant.apply_vore_prefs() else return ..() @@ -329,6 +372,9 @@ 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.") + 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) @@ -381,6 +427,7 @@ unattached_flesh.Cut() occupant = null + clonemind = null /obj/machinery/clonepod/proc/malfunction() var/mob/living/mob_occupant = occupant @@ -391,13 +438,14 @@ 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) to_chat(mob_occupant, "Agony blazes across your consciousness as your body is torn apart.
Is this what dying is like? Yes it is.
") playsound(src.loc, 'sound/machines/warning-buzzer.ogg', 50, 0) 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/relaymove(mob/user) @@ -416,11 +464,14 @@ SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of, ERROR: John Doe, prematurely." ,0)) mob_occupant.apply_vore_prefs() 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)) + 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) @@ -436,7 +487,9 @@ /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) diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 039152f5..dede5724 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -7,25 +7,24 @@ icon_keyboard = "med_key" circuit = /obj/item/circuitboard/computer/cloning req_access = list(ACCESS_HEADS) //ONLY USED FOR RECORD DELETION RIGHT NOW. - var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. + 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 = null var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything. + var/loading = 0 // Nice loading text var/autoprocess = 0 - var/list/records = list() light_color = LIGHT_COLOR_BLUE /obj/machinery/computer/cloning/Initialize() . = ..() updatemodules(TRUE) - var/obj/item/circuitboard/computer/cloning/board = circuit - records = board.records /obj/machinery/computer/cloning/Destroy() @@ -39,7 +38,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 +59,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"], empty) + /obj/machinery/computer/cloning/process() if(!(scanner && LAZYLEN(pods) && autoprocess)) return @@ -68,16 +70,19 @@ scan_occupant(scanner.occupant) for(var/datum/data/record/R in records) - var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mind"]) + var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mindref"]) if(!pod) return if(pod.occupant) - continue //how though? + continue - 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) @@ -132,7 +137,9 @@ to_chat(user, "You insert [W].") playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) src.updateUsrDialog() - else if(istype(W, /obj/item/multitool)) + 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)) @@ -201,6 +208,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 +236,11 @@ if (!src.active_record) dat += "Record not found." else - dat += "

[src.active_record.fields["name"]]

" - dat += "Scan ID [src.active_record.fields["id"]] Clone
" + 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(src.active_record.fields["imp"]) @@ -239,7 +250,14 @@ dat += "Unable to locate Health Implant.

" dat += "Unique Identifier:
[src.active_record.fields["UI"]]
" - dat += "Structural Enzymes:
[src.active_record.fields["SE"]]
" + 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 += "
" @@ -296,18 +314,13 @@ else if ((href_list["scan"]) && !isnull(scanner) && scanner.is_operational()) scantemp = "" - + var/body_only = href_list["body_only"] loading = 1 src.updateUsrDialog() playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) say("Initiating scan...") - spawn(20) - src.scan_occupant(scanner.occupant) - - loading = 0 - src.updateUsrDialog() - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + addtimer(CALLBACK(src, .proc/do_scan, usr, body_only), 2 SECONDS) //No locking an open scanner. @@ -323,34 +336,42 @@ playsound(src, "terminal_type", 25, 0) src.active_record = find_record("id", href_list["view_rec"], records) if(active_record) - if(!active_record.fields["ckey"]) - records -= active_record - active_record = null - src.temp = "Record Corrupt" - else - src.menu = 3 + menu = 3 else - src.temp = "Record missing." + temp = "Record missing." else if (href_list["del_rec"]) if ((!src.active_record) || (src.menu < 3)) return if (src.menu == 3) //If we are viewing a record, confirm deletion - src.temp = "Delete record?" - src.menu = 4 - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) + 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, 0) + else + temp = "Access Denied" + menu = 2 + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else if (src.menu == 4) var/obj/item/card/id/C = usr.get_active_held_item() if (istype(C)||istype(C, /obj/item/pda)) if(src.check_access(C)) + log_cloning("[key_name(usr)] deleted [key_name(active_record.fields["mindref"])]'s cloning records from [src] at [AREACOORD(src)].") src.temp = "[src.active_record.fields["name"]] => Record deleted." src.records.Remove(active_record) active_record = null playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) src.menu = 2 - var/obj/item/circuitboard/computer/cloning/board = circuit - board.records = records else src.temp = "Access Denied." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) @@ -400,9 +421,14 @@ 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, 0) 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." @@ -410,20 +436,33 @@ else if(!pod) temp = "No Clonepods available." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else if(!CONFIG_GET(flag/revival_cloning)) + else if(!CONFIG_GET(flag/revival_cloning) && !empty) temp = "Unable to initiate cloning cycle." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else if(pod.occupant) temp = "Cloning cycle already in progress." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else if(pod.growclone(C.fields["ckey"], C.fields["name"], C.fields["UI"], C.fields["SE"], C.fields["mind"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"])) - temp = "[C.fields["name"]] => Cloning cycle in progress..." - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - records.Remove(C) - if(active_record == C) - active_record = null - menu = 1 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, 0) + records.Remove(C) + 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, 0) @@ -439,21 +478,31 @@ src.updateUsrDialog() return -/obj/machinery/computer/cloning/proc/scan_occupant(occupant) +/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 + + // 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)) - 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, 0) return - if(mob_occupant.suiciding || mob_occupant.hellbound) + if(!body_only && (mob_occupant.suiciding || mob_occupant.hellbound)) scantemp = "Subject's brain is not responding to scanning stimuli." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return @@ -461,14 +510,14 @@ scantemp = "Subject no longer contains the fundamental materials required to create a living clone." playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) return - if ((!mob_occupant.ckey) || (!mob_occupant.client)) + if (!body_only && isnull(mob_occupant.mind)) scantemp = "Mental interface failure." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return - if (find_record("ckey", mob_occupant.ckey, records)) - scantemp = "Subject already in database." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return +// if (find_record("ckey", mob_occupant.ckey, records)) +// scantemp = "Subject already in database." +// playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) +// return var/datum/data/record/R = new() if(dna.species) @@ -481,7 +530,7 @@ var/datum/species/rando_race = pick(GLOB.roundstart_races) R.fields["mrace"] = rando_race.type - R.fields["ckey"] = mob_occupant.ckey + //R.fields["ckey"] = mob_occupant.ckey R.fields["name"] = mob_occupant.real_name R.fields["id"] = copytext(md5(mob_occupant.real_name), 2, 6) R.fields["UE"] = dna.unique_enzymes @@ -495,22 +544,32 @@ var/datum/quirk/T = V R.fields["quirks"][T.type] = T.clone_data() - if (!isnull(mob_occupant.mind)) //Save that mind so traitors can continue traitoring after cloning. - R.fields["mind"] = "[REF(mob_occupant.mind)]" + R.fields["mindref"] = "[REF(mob_occupant.mind)]" + R.fields["last_death"] = mob_occupant.stat == DEAD ? mob_occupant.mind.last_death : -1 + R.fields["body_only"] = 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)]" + 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." src.records += R - var/obj/item/circuitboard/computer/cloning/board = circuit - board.records = records - scantemp = "Subject successfully scanned." + 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, 0) diff --git a/code/game/machinery/exp_cloner.dm b/code/game/machinery/exp_cloner.dm index baa7ed27..d5388035 100644 --- a/code/game/machinery/exp_cloner.dm +++ b/code/game/machinery/exp_cloner.dm @@ -9,7 +9,7 @@ internal_radio = FALSE //Start growing a human clone in the pod! -/obj/machinery/clonepod/experimental/growclone(ckey, clonename, ui, se, datum/species/mrace, list/features, factions) +/obj/machinery/clonepod/experimental/growclone(clonename, ui, mutation_index, mindref, last_death, blood_type, datum/species/mrace, list/features, factions, list/quirks, datum/bank_account/insurance) if(panel_open) return FALSE if(mess || attempting) @@ -20,7 +20,7 @@ var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) - H.hardset_dna(ui, se, H.real_name, null, mrace, features) + H.hardset_dna(ui, mutation_index, H.real_name, blood_type, mrace, features) if(efficiency > 2) var/list/unclean_mutations = (GLOB.not_good_mutations|GLOB.bad_mutations) @@ -292,7 +292,7 @@ temp = "Cloning cycle already in progress." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else - pod.growclone(null, mob_occupant.real_name, dna.uni_identity, dna.mutation_index, clone_species, dna.features, mob_occupant.faction) + pod.growclone(mob_occupant.real_name, dna.uni_identity, dna.mutation_index, null, null, dna.blood_type, clone_species, dna.features, mob_occupant.faction) temp = "[mob_occupant.real_name] => Cloning data sent to pod." playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) diff --git a/code/game/world.dm b/code/game/world.dm index e9789bc4..958ef849 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -100,6 +100,7 @@ GLOBAL_VAR(restart_counter) GLOB.picture_log_directory = "data/picture_logs/[override_dir]" GLOB.world_game_log = "[GLOB.log_directory]/game.log" + GLOB.world_cloning_log = "[GLOB.log_directory]/cloning.log" GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" diff --git a/config/config.txt b/config/config.txt index 5dda144c..819bc01a 100644 --- a/config/config.txt +++ b/config/config.txt @@ -146,6 +146,9 @@ LOG_MANIFEST ## Enable logging pictures # LOG_PICTURES +## log cloning actions +LOG_CLONING + ##Log camera pictures - Must have picture logging enabled PICTURE_LOGGING_CAMERA