#define AUTOCLONING_MINIMAL_LEVEL 4 /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_HEADS) //ONLY USED FOR RECORD DELETION RIGHT NOW. var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. var/clonepod_type = /obj/machinery/clonepod var/list/pods //Linked cloning pods var/temp = "Inactive" var/scantemp_ckey var/scantemp_name var/scantemp = "Inactive" var/menu = 1 //Which menu screen to display 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/use_records = TRUE // Old experimental cloner. var/list/records = list() light_color = LIGHT_COLOR_BLUE /obj/machinery/computer/cloning/Initialize(mapload) . = ..() updatemodules(TRUE) var/obj/item/circuitboard/computer/cloning/board = circuit records = board.records /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) return for(var/P in pods) var/obj/machinery/clonepod/pod = P if(pod.occupant && pod.get_clone_mind == CLONEPOD_GET_MIND && pod.clonemind == mind) return null if(pod.is_operational() && !(pod.occupant || pod.mess)) return pod /obj/machinery/computer/cloning/proc/HasEfficientPod() if(!pods) return 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) return 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 /obj/machinery/computer/cloning/process() if(!(scanner && LAZYLEN(pods) && autoprocess)) return for(var/datum/data/record/R in records) var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mind"]) if(!pod) return 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["blood_type"], R.fields["mrace"], R.fields["features"], R.fields["factions"], R.fields["quirks"], R.fields["bank_account"], R.fields["traumas"])) temp = "[R.fields["name"]] => Cloning cycle in progress..." records -= R /obj/machinery/computer/cloning/proc/updatemodules(findfirstcloner) src.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 for(var/direction in GLOB.cardinals) podf = locate(clonepod_type, get_step(src, direction)) if(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 (!src.diskette) if (!user.transferItemToLoc(W,src)) return src.diskette = W to_chat(user, "You insert [W].") playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) else if(W.tool_behaviour == TOOL_MULTITOOL) if(istype(W.buffer, clonepod_type)) if(get_area(W.buffer) != get_area(src)) to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") W.buffer = null return to_chat(user, "-% Successfully linked [W.buffer] with [src] %-") var/obj/machinery/clonepod/pod = W.buffer if(pod.connected) pod.connected.DetachCloner(pod) AttachCloner(pod) else W.buffer = src to_chat(user, "-% Successfully stored [REF(W.buffer)] [W.buffer] in buffer %-") return else return ..() /obj/machinery/computer/cloning/AltClick(mob/user) . = ..() EjectDisk(user) /obj/machinery/computer/cloning/proc/EjectDisk(mob/user) if(diskette) scantemp = "Disk Ejected" diskette.forceMove(drop_location()) usr.put_in_active_hand(diskette) diskette = null playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) /obj/machinery/computer/cloning/proc/Save(mob/user, target) var/datum/data/record/GRAB = null for(var/datum/data/record/record in records) if(record.fields["id"] == target) GRAB = record break else continue if(!GRAB || !GRAB.fields) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) scantemp = "Failed saving to disk: Data Corruption" return FALSE if(!diskette || diskette.read_only) scantemp = !diskette ? "Failed saving to disk: No disk." : "Failed saving to disk: Disk refuses override attempt." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return diskette.fields = GRAB.fields.Copy() diskette.name = "data disk - '[src.diskette.fields["name"]]'" scantemp = "Saved to disk successfully." playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) /obj/machinery/computer/cloning/proc/DeleteRecord(mob/user, target) var/datum/data/record/GRAB = null for(var/datum/data/record/record in records) if(record.fields["id"] == target) GRAB = record break else continue if(!GRAB) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) scantemp = "Cannot delete: Data Corrupted." return FALSE var/obj/item/card/id/C = usr.get_idcard(hand_first = TRUE) if(istype(C) || istype(C, /obj/item/pda) || istype(C, /obj/item/modular_computer/tablet)) if(check_access(C)) scantemp = "[GRAB.fields["name"]] => Record deleted." records.Remove(GRAB) playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) var/obj/item/circuitboard/computer/cloning/board = circuit board.records = records return TRUE scantemp = "Cannot delete: Access Denied." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) /obj/machinery/computer/cloning/proc/Load(mob/user) if(!diskette || !istype(diskette.fields) || !diskette.fields["name"] || !diskette.fields) scantemp = "Failed loading: Load error." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return for(var/datum/data/record/R in records) if(R.fields["key"] == diskette.fields["key"]) scantemp = "Failed loading: Data already exists!" return FALSE var/datum/data/record/R = new(src) for(var/key in diskette.fields) R.fields[key] = diskette.fields[key] records += R scantemp = "Loaded into internal storage successfully." var/obj/item/circuitboard/computer/cloning/board = circuit board.records = records playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) /obj/machinery/computer/cloning/proc/Clone(mob/user, target) var/datum/data/record/C = find_record("id", target, records) //Look for that player! They better be dead! if(C) 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 = "Error: No Clonepods detected." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else if(!pod) temp = "Error: No Clonepods available." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else if(!CONFIG_GET(flag/revival_cloning)) temp = "Error: Unable to initiate cloning cycle." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else if(pod.occupant) temp = "Warning: 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["blood_type"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"], C.fields["bank_account"], C.fields["traumas"])) temp = "Notice: [C.fields["name"]] => Cloning cycle in progress..." playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) records.Remove(C) else temp = "Error: [C.fields["name"]] => Initialisation failure." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) else temp = "Failed to clone: Data corrupted." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) . = TRUE /obj/machinery/computer/cloning/proc/Toggle_lock(mob/user) if(!scanner.is_operational()) return if(!scanner.locked && !scanner.occupant) //I figured out that if you're fast enough, you can lock an open pod return scanner.locked = !scanner.locked playsound(src, scanner.locked ? 'sound/machines/terminal_prompt_deny.ogg' : 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) . = TRUE /obj/machinery/computer/cloning/proc/Scan(mob/user) if(!scanner.is_operational() || !scanner.occupant) return scantemp = "[scantemp_name] => Scanning..." loading = TRUE playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) say("Initiating scan...") var/prev_locked = scanner.locked scanner.locked = TRUE addtimer(CALLBACK(src, .proc/finish_scan, scanner.occupant, prev_locked), 2 SECONDS) . = TRUE /obj/machinery/computer/cloning/proc/Toggle_autoprocess(mob/user) autoprocess = !autoprocess if(autoprocess) START_PROCESSING(SSmachines, src) playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) else STOP_PROCESSING(SSmachines, src) playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) . = TRUE /obj/machinery/computer/cloning/ui_data(mob/user) var/list/data = list() data["useRecords"] = use_records var/list/records_to_send = list() if(use_records) if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) data["hasAutoprocess"] = TRUE if(length(records)) for(var/datum/data/record/R in records) var/list/record_entry = list() record_entry["name"] = "[R.fields["name"]]" record_entry["id"] = "[R.fields["id"]]" var/obj/item/implant/health/H = locate(R.fields["imp"]) if(H && istype(H)) record_entry["damages"] = H.sensehealth(TRUE) else record_entry["damages"] = FALSE record_entry["UI"] = "[R.fields["UI"]]" record_entry["UE"] = "[R.fields["UE"]]" record_entry["blood_type"] = "[R.fields["blood_type"]]" records_to_send += list(record_entry) data["records"] = records_to_send else data["records"] = list() if(diskette && diskette.fields) var/list/disk_data = list() disk_data["name"] = "[diskette.fields["name"]]" disk_data["id"] = "[diskette.fields["id"]]" disk_data["UI"] = "[diskette.fields["UI"]]" disk_data["UE"] = "[diskette.fields["UE"]]" disk_data["blood_type"] = "[diskette.fields["blood_type"]]" data["diskData"] = disk_data else data["diskData"] = list() else data["hasAutoprocess"] = FALSE data["autoprocess"] = autoprocess var/list/lack_machine = list() if(isnull(src.scanner)) lack_machine += "ERROR: No Scanner Detected!" if(!LAZYLEN(pods)) lack_machine += "ERROR: No Pod Detected!" data["lacksMachine"] = lack_machine data["temp"] = temp var/build_temp = null var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner?.occupant) if(scanner_occupant?.ckey != scantemp_ckey || scanner_occupant?.name != scantemp_name) if(use_records) build_temp = "Ready to Scan" scantemp_ckey = scanner_occupant?.ckey scantemp_name = scanner_occupant?.name else build_temp = "Ready to Clone" scantemp = "[scanner_occupant] => [build_temp]" data["scanTemp"] = scantemp data["scannerLocked"] = scanner?.locked data["hasOccupant"] = scanner?.occupant data["recordsLength"] = "View Records ([length(records)])" return data /obj/machinery/computer/cloning/ui_act(action, params) if(..()) return switch(action) if("toggle_autoprocess") Toggle_autoprocess(usr) if("scan") Scan(usr) if("toggle_lock") Toggle_lock(usr) if("clone") Clone(usr, params["target"]) if("delrecord") DeleteRecord(usr, params["target"]) if("save") Save(usr, params["target"]) if("load") Load(usr) if("eject") EjectDisk(usr) /obj/machinery/computer/cloning/ui_interact(mob/user, datum/tgui/ui) if(..()) return updatemodules(TRUE) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "CloningConsole", "Cloning System Control") ui.open() /obj/machinery/computer/cloning/proc/finish_scan(mob/living/L, prev_locked) if(!scanner || !L) return src.add_fingerprint(usr) if(use_records) scan_occupant(L) else clone_occupant(L) loading = FALSE scanner.locked = prev_locked playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) /obj/machinery/computer/cloning/proc/scan_occupant(occupant) 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() if(I) has_bank_account = I.registered_account if(isbrain(mob_occupant)) dna = B.stored_dna if(!can_scan(dna, mob_occupant, FALSE, has_bank_account)) 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["ckey"] = mob_occupant.ckey 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["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 if (!isnull(mob_occupant.mind)) //Save that mind so traitors can continue traitoring after cloning. R.fields["mind"] = "[REF(mob_occupant.mind)]" //Add an implant if needed var/obj/item/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)]" src.records += R var/obj/item/circuitboard/computer/cloning/board = circuit board.records = records scantemp = "Subject successfully scanned." playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) //Used by the experimental cloning computer. /obj/machinery/computer/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(!can_scan(dna, mob_occupant, TRUE)) 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, 0) else if(!pod) temp = "No Clonepods available." 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 pod.growclone(null, mob_occupant.real_name, dna.uni_identity, dna.mutation_index, 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) /obj/machinery/computer/cloning/proc/can_scan(datum/dna/dna, mob/living/mob_occupant, experimental = FALSE, datum/bank_account/account) if(!istype(dna)) scantemp = "Unable to locate valid genetic data." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return if(!experimental) if(mob_occupant.suiciding || mob_occupant.hellbound) scantemp = "Subject's brain is not responding to scanning stimuli." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return if((HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && (src.scanner.scan_level < 2)) scantemp = "Subject no longer contains the fundamental materials required to create a living clone." playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) return if (!experimental) if(!mob_occupant.ckey || !mob_occupant.client) scantemp = "Mental interface failure." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return if (find_record("ckey", mob_occupant.ckey, records)) scantemp = "Subject already in database." playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) return if(SSeconomy.full_ancap && !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, 0) return return TRUE //Prototype cloning console, much more rudimental and lacks modern functions such as saving records, autocloning, or safety checks. /obj/machinery/computer/cloning/prototype name = "prototype cloning console" desc = "Used to operate an experimental cloner." icon_screen = "dna" icon_keyboard = "med_key" circuit = /obj/item/circuitboard/computer/cloning/prototype clonepod_type = /obj/machinery/clonepod/experimental use_records = FALSE //Wait, so you tell me it lacks records but you never set it as false?