Files
CHOMPStation2/code/game/machinery/cloning.dm
2025-09-26 15:21:19 +02:00

521 lines
17 KiB
Plaintext

//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 (?)
//Find a dead mob with a brain and client.
/proc/find_dead_player(var/find_key)
if(isnull(find_key))
return
var/mob/selected = null
for(var/mob/living/M in GLOB.player_list)
//Dead people only thanks!
if((M.stat != 2) || (!M.client))
continue
//They need a brain!
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!H.has_brain())
continue
if(M.ckey == find_key)
selected = M
break
return selected
#define MINIMUM_HEAL_LEVEL 40
/obj/machinery/clonepod
name = "cloning pod"
desc = "An electronically-lockable pod for growing organic tissue."
density = TRUE
anchored = TRUE
circuit = /obj/item/circuitboard/clonepod
icon = 'icons/obj/cloning.dmi'
icon_state = "pod_0"
req_access = list(ACCESS_GENETICS) // For premature unlocking.
VAR_PRIVATE/datum/weakref/weakref_occupant = null
var/heal_level = 20 // The clone is released once its health reaches this level.
var/heal_rate = 1
var/locked = 0
var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine.
var/mess = 0 // Need to clean out it if it's full of exploded clone.
var/attempting = 0 // One clone attempt at a time thanks
var/eject_wait = 0 // Don't eject them as soon as they are created fuckkk
var/list/containers = list() // Beakers for our liquid biomass
var/container_limit = 3 // How many beakers can the machine hold?
var/speed_coeff
var/efficiency
/obj/machinery/clonepod/Initialize(mapload)
. = ..()
default_apply_parts()
update_icon()
/obj/machinery/clonepod/proc/set_occupant(var/mob/living/L)
SHOULD_NOT_OVERRIDE(TRUE)
if(!L)
weakref_occupant = null
return
weakref_occupant = WEAKREF(L)
/obj/machinery/clonepod/proc/get_occupant()
RETURN_TYPE(/mob/living)
SHOULD_NOT_OVERRIDE(TRUE)
return weakref_occupant?.resolve()
/obj/machinery/clonepod/attack_ai(mob/user as mob)
add_hiddenprint(user)
return attack_hand(user)
/obj/machinery/clonepod/attack_hand(mob/user as mob)
var/mob/living/occupant = get_occupant()
if((isnull(occupant)) || (stat & NOPOWER))
return
if((!isnull(occupant)) && (occupant.stat != 2))
var/completion = (100 * ((occupant.health + 50) / (heal_level + 100))) // Clones start at -150 health
to_chat(user, "Current clone cycle is [round(completion)]% complete.")
return
//Start growing a human clone in the pod!
/obj/machinery/clonepod/proc/growclone(var/datum/transhuman/body_record/BR)
if(mess || attempting)
return 0
var/datum/mind/clonemind = locate(BR.mydna.mind)
if(!istype(clonemind, /datum/mind)) //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) != BR.ckey)
return 0
else
for(var/mob/observer/dead/G in GLOB.player_list)
if(G.ckey == BR.ckey)
if(G.can_reenter_corpse)
break
else
return 0
for(var/modifier_type in BR.genetic_modifiers) //Can't be cloned, even if they had a previous scan
if(istype(modifier_type, /datum/modifier/no_clone))
return 0
// Remove biomass when the cloning is started, rather than when the guy pops out
remove_biomass(CLONE_BIOMASS)
attempting = 1 //One at a time!!
locked = 1
eject_wait = 1
spawn(30)
eject_wait = 0
//Get the clone body ready, let's calculate their health so the pod doesn't immediately eject them!!!
var/mob/living/carbon/human/H = BR.produce_human_mob(src,FALSE, FALSE, "clone ([rand(0,999)])")
SEND_SIGNAL(H, COMSIG_HUMAN_DNA_FINALIZED)
//Get the clone body ready
var/damage_to_deal = H.getMaxHealth() * 1.5 //If you have 100, you get 150. Have 200? Get 300. 25hp? get 37.5
H.adjustCloneLoss(damage_to_deal) // New damage var so you can't eject a clone early then stab them to abuse the current damage system --NeoFite
H.Paralyse(4)
H.updatehealth()
H.set_cloned_appearance()
// Move mind to body along with key
clonemind.transfer_to(H)
H.ckey = BR.ckey
to_chat(H, span_warning(span_bold("Consciousness slowly creeps over you as your body regenerates.") + "<br>" + span_bold(span_large("Your recent memories are fuzzy, and it's hard to remember anything from today...")) + \
"<br>" + span_notice(span_italics("So this is what cloning feels like?"))))
// -- Mode/mind specific stuff goes here
callHook("clone", list(H))
update_antag_icons(H.mind)
// -- End mode specific stuff
// A modifier is added which makes the new clone be unrobust.
// Upgraded cloners can reduce the time of the modifier, up to 80%
var/modifier_lower_bound = 25 MINUTES
var/modifier_upper_bound = 40 MINUTES
var/clone_sickness_length = abs(((heal_level - 20) / 100 ) - 1)
clone_sickness_length = between(0.2, clone_sickness_length, 1.0) // Caps it off just incase.
modifier_lower_bound = round(modifier_lower_bound * clone_sickness_length, 1)
modifier_upper_bound = round(modifier_upper_bound * clone_sickness_length, 1)
H.add_modifier(H.species.cloning_modifier, rand(modifier_lower_bound, modifier_upper_bound))
H.add_modifier(/datum/modifier/cloned)
// Finished!
update_icon()
set_occupant(H)
attempting = 0
return 1
//Grow clones to maturity then kick them out. FREELOADERS
/obj/machinery/clonepod/process()
var/mob/living/occupant = get_occupant()
if(stat & NOPOWER) //Autoeject if power is lost
if(occupant)
locked = 0
go_out()
return
if((occupant) && (occupant.loc == src))
if((occupant.stat == DEAD) || (occupant.suiciding) || !occupant.key) //Autoeject corpses and suiciding dudes.
locked = 0
go_out()
connected_message("Clone Rejected: Deceased.")
return
else if(occupant.health < heal_level && occupant.getCloneLoss() > 0)
occupant.Paralyse(4)
//Slowly get that clone healed and finished.
occupant.adjustCloneLoss(-2 * heal_rate)
//Premature clones may have brain damage.
occupant.adjustBrainLoss(-(CEILING(0.5*heal_rate, 1)))
//So clones don't die of oxyloss in a running pod.
if(occupant.reagents.get_reagent_amount(REAGENT_ID_INAPROVALINE) < 30)
occupant.reagents.add_reagent(REAGENT_ID_INAPROVALINE, 60)
occupant.Sleeping(30)
//Also heal some oxyloss ourselves because inaprovaline is so bad at preventing it!!
occupant.adjustOxyLoss(-4)
use_power(7500) //This might need tweaking.
return
else if((occupant.health >= heal_level || occupant.health == occupant.getMaxHealth()) && (!eject_wait))
playsound(src, 'sound/machines/medbayscanner1.ogg', 50, 1)
audible_message("\The [src] signals that the cloning process is complete.", runemessage = "ding")
connected_message("Cloning Process Complete.")
locked = 0
go_out()
return
else if((!occupant) || (occupant.loc != src))
set_occupant(null)
if(locked)
locked = 0
return
return
//Let's unlock this early I guess. Might be too early, needs tweaking.
/obj/machinery/clonepod/attackby(obj/item/W as obj, mob/user as mob)
var/mob/living/occupant = get_occupant()
if(isnull(occupant))
if(default_deconstruction_screwdriver(user, W))
return
if(default_deconstruction_crowbar(user, W))
return
if(default_part_replacement(user, W))
return
if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda))
if(!check_access(W))
to_chat(user, span_warning("Access Denied."))
return
if((!locked) || (isnull(occupant)))
return
if((occupant.health < -20) && (occupant.stat != 2))
to_chat(user, span_warning("Access Refused."))
return
else
locked = 0
to_chat(user, "System unlocked.")
else if(istype(W,/obj/item/reagent_containers/glass))
if(LAZYLEN(containers) >= container_limit)
to_chat(user, span_warning("\The [src] has too many containers loaded!"))
else if(do_after(user, 1 SECOND, target = src))
user.visible_message("[user] has loaded \the [W] into \the [src].", "You load \the [W] into \the [src].")
containers += W
user.drop_item()
W.forceMove(src)
return
else if(W.has_tool_quality(TOOL_WRENCH))
if(locked && (anchored || occupant))
to_chat(user, span_warning("Can not do that while [src] is in use."))
else
if(anchored)
anchored = FALSE
connected.pods -= src
connected = null
else
anchored = TRUE
playsound(src, W.usesound, 100, 1)
if(anchored)
user.visible_message("[user] secures [src] to the floor.", "You secure [src] to the floor.")
else
user.visible_message("[user] unsecures [src] from the floor.", "You unsecure [src] from the floor.")
else if(istype(W, /obj/item/multitool))
var/obj/item/multitool/M = W
M.connecting = src
to_chat(user, span_notice("You load connection data from [src] to [M]."))
M.update_icon()
return
else
..()
/obj/machinery/clonepod/emag_act(var/remaining_charges, var/mob/user)
if(isnull(get_occupant()))
return
to_chat(user, "You force an emergency ejection.")
locked = 0
go_out()
return 1
//Put messages in the connected computer's temp var for display.
/obj/machinery/clonepod/proc/connected_message(var/message)
if((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning)))
return 0
if(!message)
return 0
connected.temp = "[name] : [message]"
return 1
/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)
/obj/machinery/clonepod/proc/get_completion()
. = (100 * ((get_occupant().health + 100) / (heal_level + 100)))
/obj/machinery/clonepod/verb/eject()
set name = "Eject Cloner"
set category = "Object"
set src in oview(1)
if(usr.stat != 0)
return
go_out()
add_fingerprint(usr)
return
/obj/machinery/clonepod/proc/go_out()
if(locked)
return
if(mess) //Clean that mess and dump those gibs!
mess = 0
gibs(src.loc)
update_icon()
return
var/mob/living/occupant = get_occupant()
if(!(occupant))
return
if(occupant.client)
occupant.client.eye = occupant.client.mob
occupant.client.perspective = MOB_PERSPECTIVE
occupant.forceMove(get_turf(src))
eject_wait = 0 //If it's still set somehow.
if(ishuman(occupant)) //Need to be safe.
var/mob/living/carbon/human/patient = occupant
if(!(patient.species.flags & NO_DNA)) //If, for some reason, someone makes a genetically-unalterable clone, let's not make them permanently disabled.
domutcheck(occupant) //Waiting until they're out before possible transforming.
occupant.UpdateAppearance()
set_occupant(null)
update_icon()
return
// Returns the total amount of biomass reagent in all of the pod's stored containers
/obj/machinery/clonepod/proc/get_biomass()
var/biomass_count = 0
if(LAZYLEN(containers))
for(var/obj/item/reagent_containers/glass/G in containers)
for(var/datum/reagent/R in G.reagents.reagent_list)
if(R.id == REAGENT_ID_BIOMASS)
biomass_count += R.volume
return biomass_count
// Removes [amount] biomass, spread across all containers. Doesn't have any check that you actually HAVE enough biomass, though.
/obj/machinery/clonepod/proc/remove_biomass(var/amount = CLONE_BIOMASS) //Just in case it doesn't get passed a new amount, assume one clone
var/to_remove = 0 // Tracks how much biomass has been found so far
if(LAZYLEN(containers))
for(var/obj/item/reagent_containers/glass/G in containers)
if(to_remove < amount) //If we have what we need, we can stop. Checked every time we switch beakers
for(var/datum/reagent/R in G.reagents.reagent_list)
if(R.id == REAGENT_ID_BIOMASS) // Finds Biomass
var/need_remove = max(0, amount - to_remove) //Figures out how much biomass is in this container
if(R.volume >= need_remove) //If we have more than enough in this beaker, only take what we need
R.remove_self(need_remove)
to_remove = amount
else //Otherwise, take everything and move on
to_remove += R.volume
R.remove_self(R.volume)
else
continue
else
return 1
return 0
// Empties all of the beakers from the cloning pod, used to refill it
/obj/machinery/clonepod/verb/empty_beakers()
set name = "Eject Beakers"
set category = "Object"
set src in oview(1)
if(usr.stat != 0)
return
add_fingerprint(usr)
drop_beakers()
return
// Actually does all of the beaker dropping
// Returns 1 if it succeeds, 0 if it fails. Added in case someone wants to add messages to the user.
/obj/machinery/clonepod/proc/drop_beakers()
if(LAZYLEN(containers))
var/turf/T = get_turf(src)
if(T)
for(var/obj/item/reagent_containers/glass/G in containers)
G.forceMove(T)
containers -= G
return 1
return 0
/obj/machinery/clonepod/proc/malfunction()
var/mob/living/occupant = get_occupant()
if(occupant)
connected_message("Critical Error!")
mess = 1
update_icon()
occupant.ghostize()
QDEL_IN(occupant, 0.5 SECONDS)
/obj/machinery/clonepod/relaymove(mob/user)
if(user.stat)
return
go_out()
/obj/machinery/clonepod/emp_act(severity)
if(prob(100/severity))
malfunction()
..()
/obj/machinery/clonepod/ex_act(severity)
switch(severity)
if(1.0)
for(var/atom/movable/A as mob|obj in src)
A.forceMove(get_turf(src))
ex_act(severity)
qdel(src)
return
if(2.0)
if(prob(50))
for(var/atom/movable/A as mob|obj in src)
A.forceMove(get_turf(src))
ex_act(severity)
qdel(src)
return
if(3.0)
if(prob(25))
for(var/atom/movable/A as mob|obj in src)
A.forceMove(get_turf(src))
ex_act(severity)
qdel(src)
return
return
/obj/machinery/clonepod/update_icon()
..()
icon_state = "pod_0"
if(get_occupant() && !(stat & NOPOWER))
icon_state = "pod_1"
else if(mess)
icon_state = "pod_g"
/obj/machinery/clonepod/full/Initialize(mapload)
. = ..()
for(var/i = 1 to container_limit)
containers += new /obj/item/reagent_containers/glass/bottle/biomass(src)
//Health Tracker Implant
/obj/item/implant/health
name = "health implant"
known_implant = TRUE
var/healthstring = ""
/obj/item/implant/health/proc/sensehealth()
if(!implanted)
return "ERROR"
else
if(isliving(implanted))
var/mob/living/L = implanted
healthstring = "[round(L.getOxyLoss())] - [round(L.getFireLoss())] - [round(L.getToxLoss())] - [round(L.getBruteLoss())]"
if(!healthstring)
healthstring = "ERROR"
return healthstring
#undef MINIMUM_HEAL_LEVEL
/*
* Diskette Box
*/
/obj/item/storage/box/disks
name = "Diskette Box"
icon_state = "disk_kit"
/obj/item/storage/box/disks/Initialize(mapload)
. = ..()
new /obj/item/disk/body_record(src)
new /obj/item/disk/body_record(src)
new /obj/item/disk/body_record(src)
new /obj/item/disk/body_record(src)
new /obj/item/disk/body_record(src)
new /obj/item/disk/body_record(src)
new /obj/item/disk/body_record(src)
/*
* Manual -- A big ol' manual.
*/
/obj/item/paper/Cloning
name = "H-87 Cloning Apparatus Manual"
info = {"<h4>Getting Started</h4>
Congratulations, your station has purchased the H-87 industrial cloning device!<br>
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!<br>
<b>That's all there is to it!</b><br>
<i>Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.</i><br>
<p>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.</p>
<h4>Cloning from a profile</h4>
Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.<br>
Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.<br>
<br>
<p>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.</p><br>
<i>Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.</i><br>
<h4>Profile Management</h4>
<p>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.</p><br>
<i>A good diskette is a great way to counter aforementioned genetic drift!</i><br>
<br>"} + span_small("This technology produced under license from Thinktronic Systems, LTD.")
//SOME SCRAPS I GUESS
/* EMP grenade/spell effect
if(istype(A, /obj/machinery/clonepod))
A:malfunction()
*/