//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_BIOMASS 150
#define BIOMASS_BASE_AMOUNT 50 // How much biomass a BIOMASSABLE item gives the cloning pod
// Not a comprehensive list: Further PRs should add appropriate items here.
// Meat as usual, monstermeat covers goliath, xeno, spider, bear meat
GLOBAL_LIST_INIT(cloner_biomass_items, list(\
/obj/item/reagent_containers/food/snacks/meat,\
/obj/item/reagent_containers/food/snacks/monstermeat,
/obj/item/reagent_containers/food/snacks/carpmeat,
/obj/item/reagent_containers/food/snacks/salmonmeat,
/obj/item/reagent_containers/food/snacks/catfishmeat,
/obj/item/reagent_containers/food/snacks/tofurkey))
#define MINIMUM_HEAL_LEVEL 40
#define CLONE_INITIAL_DAMAGE 190
#define BRAIN_INITIAL_DAMAGE 90 // our minds are too feeble for 190
/obj/machinery/clonepod
anchored = TRUE
name = "cloning pod"
desc = "An electronically-lockable pod for growing organic tissue."
density = TRUE
icon = 'icons/obj/cloning.dmi'
icon_state = "pod_idle"
req_access = list(ACCESS_MEDICAL) //For premature unlocking.
var/mob/living/carbon/human/occupant
var/heal_level //The clone is released once its health reaches this level.
var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine.
var/mess = FALSE //Need to clean out it if it's full of exploded clone.
var/attempting = FALSE //One clone attempt at a time thanks
var/biomass = 0
var/speed_coeff
var/efficiency
var/datum/mind/clonemind
var/grab_ghost_when = CLONER_MATURE_CLONE
var/obj/item/radio/Radio
var/radio_announce = TRUE
var/obj/effect/countdown/clonepod/countdown
var/list/brine_types = list("corazone", "perfluorodecalin", "epinephrine", "salglu_solution") //stops heart attacks, heart failure, shock, and keeps their O2 levels normal
var/list/missing_organs = list()
var/organs_number = 0
light_color = LIGHT_COLOR_PURE_GREEN
/obj/machinery/clonepod/power_change()
..() //we don't check return here because we also care about the BROKEN flag
if(!(stat & (BROKEN|NOPOWER)))
set_light(2)
else
set_light(0)
/obj/machinery/clonepod/biomass
biomass = CLONE_BIOMASS
/obj/machinery/clonepod/Initialize(mapload)
. = ..()
countdown = new(src)
Radio = new /obj/item/radio(src)
Radio.listening = FALSE
Radio.config(list("Medical" = 0))
Radio.follow_target = src
component_parts = list()
component_parts += new /obj/item/circuitboard/clonepod(null)
component_parts += new /obj/item/stock_parts/scanning_module(null)
component_parts += new /obj/item/stock_parts/scanning_module(null)
component_parts += new /obj/item/stock_parts/manipulator(null)
component_parts += new /obj/item/stock_parts/manipulator(null)
component_parts += new /obj/item/stack/sheet/glass(null)
component_parts += new /obj/item/stack/cable_coil(null, 1)
component_parts += new /obj/item/stack/cable_coil(null, 1)
RefreshParts()
update_icon()
/obj/machinery/clonepod/upgraded/Initialize(mapload)
. = ..()
component_parts = list()
component_parts += new /obj/item/circuitboard/clonepod(null)
component_parts += new /obj/item/stock_parts/scanning_module/phasic(null)
component_parts += new /obj/item/stock_parts/scanning_module/phasic(null)
component_parts += new /obj/item/stock_parts/manipulator/pico(null)
component_parts += new /obj/item/stock_parts/manipulator/pico(null)
component_parts += new /obj/item/stack/sheet/glass(null)
component_parts += new /obj/item/stack/cable_coil(null, 1)
component_parts += new /obj/item/stack/cable_coil(null, 1)
biomass = CLONE_BIOMASS
RefreshParts()
/obj/machinery/clonepod/Destroy()
if(connected)
connected.pods -= src
connected = null
if(clonemind)
UnregisterSignal(clonemind.current, COMSIG_LIVING_REVIVE)
UnregisterSignal(clonemind, COMSIG_MIND_TRANSER_TO)
QDEL_NULL(Radio)
QDEL_NULL(countdown)
QDEL_LIST_CONTENTS(missing_organs)
return ..()
/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 = max(min((efficiency * 15) + 10, 100), MINIMUM_HEAL_LEVEL)
//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
name = "Genetics Data Disk"
icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk.
var/datum/dna2/record/buf = null
var/read_only = FALSE //Well,it's still a floppy disk
/obj/item/disk/data/proc/initialize()
buf = new
buf.dna=new
/obj/item/disk/data/Destroy()
QDEL_NULL(buf)
return ..()
/obj/item/disk/data/demo
name = "data disk - 'God Emperor of Mankind'"
read_only = TRUE
/obj/item/disk/data/demo/New()
..()
initialize()
buf.types=DNA2_BUF_UE|DNA2_BUF_UI
//data = "066000033000000000AF00330660FF4DB002690"
//data = "0C80C80C80C80C80C8000000000000161FBDDEF" - Farmer Jeff
buf.dna.real_name="God Emperor of Mankind"
buf.dna.unique_enzymes = md5(buf.dna.real_name)
buf.dna.UI=list(0x066,0x000,0x033,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0xAF0,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x033,0x066,0x0FF,0x4DB,0x002,0x690,0x000,0x000,0x000,0x328,0x045,0x5FC,0x053,0x035,0x035,0x035)
//buf.dna.UI=list(0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x0C8,0x000,0x000,0x000,0x000,0x161,0xFBD,0xDEF) // Farmer Jeff
if(buf.dna.UI.len != DNA_UI_LENGTH) //If there's a disparity b/w the dna UI string lengths, 0-fill the extra blocks in this UI.
for(var/i in buf.dna.UI.len to DNA_UI_LENGTH)
buf.dna.UI += 0x000
buf.dna.ResetSE()
buf.dna.UpdateUI()
/obj/item/disk/data/monkey
name = "data disk - 'Mr. Muggles'"
read_only = 1
/obj/item/disk/data/monkey/New()
..()
initialize()
buf.types=DNA2_BUF_SE
var/list/new_SE=list(0x098,0x3E8,0x403,0x44C,0x39F,0x4B0,0x59D,0x514,0x5FC,0x578,0x5DC,0x640,0x6A4)
for(var/i=new_SE.len;i<=DNA_SE_LENGTH;i++)
new_SE += rand(1,1024)
buf.dna.SE=new_SE
buf.dna.SetSEValueRange(GLOB.monkeyblock,0xDAC, 0xFFF)
//Disk stuff.
/obj/item/disk/data/New()
..()
var/diskcolor = pick(0,1,2)
icon_state = "datadisk[diskcolor]"
/obj/item/disk/data/attack_self(mob/user as mob)
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)
. = ..()
if(mess)
. += "It's filled with blood and viscera. You swear you can see it moving..."
if(HAS_TRAIT(src, TRAIT_CMAGGED))
. += "Yellow ooze is dripping out of the synthmeat storage chamber..."
if(!occupant || stat & (NOPOWER|BROKEN))
return
if(occupant && occupant.stat != DEAD)
. += "Current clone cycle is [round(get_completion())]% complete."
/obj/machinery/clonepod/return_air() //non-reactive air
RETURN_TYPE(/datum/gas_mixture)
var/datum/gas_mixture/GM = new
GM.nitrogen = MOLES_O2STANDARD + MOLES_N2STANDARD
GM.temperature = T20C
return GM
/obj/machinery/clonepod/proc/get_completion()
. = (100 * ((occupant.health + 100) / (heal_level + 100)))
/obj/machinery/clonepod/attack_ai(mob/user)
return examine(user)
//Radio Announcement
/obj/machinery/clonepod/proc/announce_radio_message(message)
if(radio_announce)
Radio.autosay(message, name, "Medical", list(z))
//Start growing a human clone in the pod!
/obj/machinery/clonepod/proc/growclone(datum/dna2/record/R)
if(mess || attempting || panel_open || stat & (NOPOWER|BROKEN))
return 0
clonemind = locate(R.mind)
if(!istype(clonemind)) //not a mind
return 0
if(clonemind.current && clonemind.current.stat != DEAD) //mind is associated with a non-dead body
return 0
if(clonemind.active) //somebody is using that mind
if(ckey(clonemind.key) != R.ckey )
return 0
if(clonemind.suicided) // and stay out!
malfunction(go_easy = 0)
return -1 // Flush the record
else
// get_ghost() will fail if they're unable to reenter their body
var/mob/dead/observer/G = clonemind.get_ghost()
if(!G)
return 0
if(biomass >= CLONE_BIOMASS)
biomass -= CLONE_BIOMASS
else
return 0
attempting = TRUE //One at a time!!
countdown.start()
if(!R.dna)
R.dna = new /datum/dna()
var/mob/living/carbon/human/H = new /mob/living/carbon/human(src)
H.set_species(R.dna.species.type)
occupant = H
if(!R.dna.real_name) //to prevent null names
R.dna.real_name = H.real_name
else
H.real_name = R.dna.real_name
H.dna = R.dna.Clone()
for(var/datum/language/L in R.languages)
H.add_language(L.name)
domutcheck(H, MUTCHK_FORCED) //Ensures species that get powers by the species proc handle_dna keep them
if(efficiency > 2 && efficiency < 5 && prob(25))
randmutb(H)
if(efficiency > 5 && prob(20))
randmutg(H)
if(efficiency < 3 && prob(50))
randmutb(H)
H.dna.UpdateSE()
H.dna.UpdateUI()
H.sync_organ_dna(1) // It's literally a fresh body as you can get, so all organs properly belong to it
H.UpdateAppearance()
check_brine()
//Get the clone body ready
maim_clone(H)
H.Paralyse(8 SECONDS)
if(grab_ghost_when == CLONER_FRESH_CLONE)
clonemind.transfer_to(H)
H.ckey = R.ckey
update_clone_antag(H) //Since the body's got the mind, update their antag stuff right now. Otherwise, wait until they get kicked out (as per the CLONER_MATURE_CLONE business) to do it.
var/message
message += "Consciousness slowly creeps over you as your body regenerates.
"
message += "So this is what cloning feels like?"
to_chat(H, "[message]")
else if(grab_ghost_when == CLONER_MATURE_CLONE)
to_chat(clonemind.current, "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.")
// Set up a soul link with the dead body to catch a revival
RegisterSignal(clonemind.current, COMSIG_LIVING_REVIVE, PROC_REF(occupant_got_revived))
RegisterSignal(clonemind, COMSIG_MIND_TRANSER_TO, PROC_REF(occupant_got_revived))
SSblackbox.record_feedback("tally", "players_revived", 1, "cloned")
update_icon()
H.suiciding = FALSE
attempting = FALSE
return 1
//Grow clones to maturity then kick them out. FREELOADERS
/obj/machinery/clonepod/process()
var/show_message = FALSE
for(var/obj/item/item in range(1, src))
if(is_type_in_list(item, GLOB.cloner_biomass_items))
qdel(item)
biomass += BIOMASS_BASE_AMOUNT
show_message = TRUE
if(show_message)
visible_message("[src] sucks in and processes the nearby biomass.")
if(stat & NOPOWER) //Autoeject if power is lost
if(occupant)
go_out()
connected_message("Clone Ejected: Loss of power.")
else if((occupant) && (occupant.loc == src))
if((occupant.stat == DEAD) || (occupant.suiciding)) //Autoeject corpses and suiciding dudes.
announce_radio_message("The cloning of [occupant] has been aborted due to unrecoverable tissue failure.")
go_out()
connected_message("Clone Rejected: Deceased.")
else if(occupant.cloneloss > (100 - heal_level))
occupant.Paralyse(8 SECONDS)
//Slowly get that clone healed and finished.
occupant.adjustCloneLoss(-((speed_coeff/2)))
// For human species that lack non-vital parts for some weird reason
if(organs_number)
var/progress = CLONE_INITIAL_DAMAGE - occupant.getCloneLoss()
progress += (100 - MINIMUM_HEAL_LEVEL)
var/milestone = CLONE_INITIAL_DAMAGE / organs_number
// Doing this as a #define so that the value can change when evaluated multiple times
#define INSTALLED (organs_number - length(missing_organs))
while((progress / milestone) > INSTALLED && length(missing_organs))
var/obj/item/organ/I = pick_n_take(missing_organs)
I.safe_replace(occupant)
#undef INSTALLED
//Premature clones may have brain damage.
occupant.adjustBrainLoss(-((speed_coeff/20)*efficiency))
check_brine()
//Also heal some oxyloss ourselves just in case!!
occupant.adjustOxyLoss(-10)
use_power(7500) //This might need tweaking.
else if((occupant.cloneloss <= (100 - heal_level)))
connected_message("Cloning Process Complete.")
announce_radio_message("The cloning cycle of [occupant] is complete.")
go_out()
else if((!occupant) || (occupant.loc != src))
occupant = null
update_icon()
use_power(200)
//Let's unlock this early I guess. Might be too early, needs tweaking.
/obj/machinery/clonepod/attackby(obj/item/I, mob/user, params)
if(exchange_parts(user, I))
return
if(I.GetID())
if(!check_access(I))
to_chat(user, "Access Denied.")
return
if(!(occupant || mess))
to_chat(user, "Error: Pod has no occupant.")
return
else
connected_message("Authorized Ejection")
announce_radio_message("An authorized ejection of [(occupant) ? occupant.real_name : "the malfunctioning pod"] has occurred")
to_chat(user, "You force an emergency ejection.")
go_out()
// A user can feed in biomass sources manually.
else if(is_type_in_list(I, GLOB.cloner_biomass_items))
if(user.drop_item())
to_chat(user, "[src] processes [I].")
biomass += BIOMASS_BASE_AMOUNT
qdel(I)
else
return ..()
/obj/machinery/clonepod/crowbar_act(mob/user, obj/item/I)
. = TRUE
default_deconstruction_crowbar(user, I)
/obj/machinery/clonepod/multitool_act(mob/user, obj/item/I)
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
if(!I.multitool_check_buffer(user))
return
var/obj/item/multitool/M = I
M.set_multitool_buffer(user, src)
/obj/machinery/clonepod/screwdriver_act(mob/user, obj/item/I)
. = TRUE
// These icon states don't really matter since we need to call update_icon() to handle panel open/closed overlays anyway.
default_deconstruction_screwdriver(user, null, null, I)
update_icon()
/obj/machinery/clonepod/wrench_act(mob/user, obj/item/I)
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
if(occupant)
to_chat(user, "Can not do that while [src] is in use.")
return
if(anchored)
WRENCH_UNANCHOR_MESSAGE
anchored = FALSE
connected.pods -= src
connected = null
else
WRENCH_ANCHOR_MESSAGE
anchored = TRUE
/obj/machinery/clonepod/emag_act(user)
malfunction()
/obj/machinery/clonepod/cmag_act(mob/user)
if(HAS_TRAIT(src, TRAIT_CMAGGED))
return
playsound(src, "sparks", 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
to_chat(user, "A droplet of bananium ooze seeps into the synthmeat storage chamber...")
ADD_TRAIT(src, TRAIT_CMAGGED, CLOWN_EMAG)
/obj/machinery/clonepod/proc/update_clone_antag(mob/living/carbon/human/H)
// Check to see if the clone's mind is an antagonist of any kind and handle them accordingly to make sure they get their spells, HUD/whatever else back.
if(H.mind in SSticker.mode.syndicates)
SSticker.mode.update_synd_icons_added()
if(H.mind in SSticker.mode.cult)
SSticker.mode.update_cult_icons_added(H.mind) // Adds the cult antag hud
SSticker.mode.add_cult_actions(H.mind) // And all the actions
if(SSticker.mode.cult_risen)
SSticker.mode.rise(H)
if(SSticker.mode.cult_ascendant)
SSticker.mode.ascend(H)
//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 = "[name] : [message]"
connected.updateUsrDialog()
return TRUE
/obj/machinery/clonepod/proc/go_out()
countdown.stop()
var/turf/T = get_turf(src)
if(mess) //Clean that mess and dump those gibs!
for(var/i in missing_organs)
var/obj/I = i
I.forceMove(T)
missing_organs.Cut()
mess = FALSE
new /obj/effect/gibspawner/generic(get_turf(src), occupant)
playsound(loc, 'sound/effects/splat.ogg', 50, 1)
update_icon()
return
if(!occupant)
return
if(grab_ghost_when == CLONER_MATURE_CLONE)
UnregisterSignal(clonemind.current, COMSIG_LIVING_REVIVE)
UnregisterSignal(clonemind, COMSIG_MIND_TRANSER_TO)
clonemind.transfer_to(occupant)
occupant.grab_ghost()
update_clone_antag(occupant)
to_chat(occupant, "You remember nothing from the time that you were dead!")
to_chat(occupant, "There is a bright flash!
\
You feel like a new being.")
if(HAS_TRAIT(src, TRAIT_CMAGGED))
playsound(loc, 'sound/items/bikehorn.ogg', 50, 1)
occupant.dna.SetSEState(GLOB.clumsyblock, TRUE, FALSE)
occupant.dna.SetSEState(GLOB.comicblock, TRUE, FALSE)
singlemutcheck(occupant, GLOB.clumsyblock, MUTCHK_FORCED)
singlemutcheck(occupant, GLOB.comicblock, MUTCHK_FORCED)
occupant.dna.default_blocks.Add(GLOB.clumsyblock) //Until Genetics fixes you, this is your life now
occupant.dna.default_blocks.Add(GLOB.comicblock)
occupant.flash_eyes(visual = 1)
clonemind = null
QDEL_LIST_CONTENTS(missing_organs)
occupant.SetLoseBreath(0) // Stop friggin' dying, gosh damn
occupant.setOxyLoss(0)
for(var/datum/disease/critical/crit in occupant.viruses)
crit.cure()
occupant.forceMove(T)
occupant.update_body()
domutcheck(occupant) //Waiting until they're out before possible notransform.
occupant.special_post_clone_handling()
occupant = null
update_icon()
/obj/machinery/clonepod/proc/malfunction(go_easy = FALSE)
if(occupant)
connected_message("Critical Error!")
announce_radio_message("Critical error! Please contact a Thinktronic Systems technician, as your warranty may be affected.")
UnregisterSignal(clonemind.current, COMSIG_LIVING_REVIVE)
UnregisterSignal(clonemind, COMSIG_MIND_TRANSER_TO)
if(!go_easy)
if(occupant.mind != clonemind)
clonemind.transfer_to(occupant)
occupant.grab_ghost() // We really just want to make you suffer.
var/message
message += "Agony blazes across your consciousness as your body is torn apart.
"
message += "Is this what dying is like? Yes it is."
to_chat(occupant, "[message]")
SEND_SOUND(occupant, sound('sound/hallucinations/veryfar_noise.ogg', 0, 1, 50))
QDEL_LIST_CONTENTS(missing_organs)
clonemind = null
spawn(40)
qdel(occupant)
playsound(loc, 'sound/machines/warning-buzzer.ogg', 50, 0)
mess = TRUE
update_icon()
/obj/machinery/clonepod/update_icon_state()
if(occupant && !(stat & NOPOWER))
icon_state = "pod_cloning"
else if(mess)
icon_state = "pod_mess"
else
icon_state = "pod_idle"
/obj/machinery/clonepod/update_overlays()
. = ..()
if(panel_open)
. += "panel_open"
/obj/machinery/clonepod/relaymove(mob/user)
if(user.stat == CONSCIOUS)
go_out()
/obj/machinery/clonepod/emp_act(severity)
if(prob(100/(severity*efficiency))) malfunction()
..()
/obj/machinery/clonepod/ex_act(severity)
..()
if(!QDELETED(src) && occupant)
go_out()
/obj/machinery/clonepod/handle_atom_del(atom/A)
if(A == occupant)
occupant = null
countdown.stop()
/obj/machinery/clonepod/deconstruct(disassembled = TRUE)
if(occupant)
go_out()
..()
/obj/machinery/clonepod/proc/occupant_got_revived()
// The old body's back in shape, time to ditch the cloning one
malfunction(go_easy = TRUE)
/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H)
QDEL_LIST_CONTENTS(missing_organs)
H.setCloneLoss(CLONE_INITIAL_DAMAGE, FALSE)
H.setBrainLoss(BRAIN_INITIAL_DAMAGE)
for(var/o in H.internal_organs)
var/obj/item/organ/O = o
if(!istype(O) || O.vital)
continue
// Let's non-specially remove all non-vital organs
// What could possibly go wrong
var/obj/item/I = O.remove(H)
// Make this support stuff that turns into items when removed
I.forceMove(src)
missing_organs += I
var/static/list/zones = list("r_arm", "l_arm", "r_leg", "l_leg")
for(var/zone in zones)
var/obj/item/organ/external/E = H.get_organ(zone)
var/obj/item/I = E.remove(H)
I.forceMove(src)
missing_organs += I
organs_number = length(missing_organs)
H.updatehealth()
/obj/machinery/clonepod/proc/check_brine()
// Clones are in a pickled bath of mild chemicals, keeping
// them alive, despite their lack of internal organs
for(var/bt in brine_types)
if(occupant.reagents.get_reagent_amount(bt) < 1)
occupant.reagents.add_reagent(bt, 1)
/*
* Genetics Diskette Box
*/
/obj/item/storage/box/disks
name = "Genetics Diskette Box"
icon_state = "disk_kit"
/obj/item/storage/box/disks/populate_contents()
for(var/i in 1 to 7)
new /obj/item/disk/data(src)
/*
* Manual -- A big ol' manual.
*/
/obj/item/paper/Cloning
name = "paper - 'H-87 Cloning Apparatus Manual"
info = {"
Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR BIO-CHIP into the subject, which may be viewed from each profile. Profile Deletion has been restricted to \[Station Head\] level access.
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.
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.