[MIRROR] Dna, Bodyrecord, Xenochi Revive Refactor (#11038)

Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-06-10 18:41:08 -07:00
committed by GitHub
parent 48b3e8f772
commit 75e167a92f
43 changed files with 1082 additions and 919 deletions

View File

@@ -34,7 +34,7 @@
icon = 'icons/obj/cloning.dmi'
icon_state = "pod_0"
req_access = list(access_genetics) // For premature unlocking.
var/mob/living/occupant
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
@@ -54,12 +54,25 @@
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))
@@ -68,27 +81,27 @@
return
//Start growing a human clone in the pod!
/obj/machinery/clonepod/proc/growclone(var/datum/dna2/record/R)
/obj/machinery/clonepod/proc/growclone(var/datum/transhuman/body_record/BR)
if(mess || attempting)
return 0
var/datum/mind/clonemind = locate(R.mind)
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) != R.ckey)
if(ckey(clonemind.key) != BR.ckey)
return 0
else
for(var/mob/observer/dead/G in player_list)
if(G.ckey == R.ckey)
if(G.ckey == BR.ckey)
if(G.can_reenter_corpse)
break
else
return 0
for(var/modifier_type in R.genetic_modifiers) //Can't be cloned, even if they had a previous scan
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
@@ -102,23 +115,20 @@
spawn(30)
eject_wait = 0
var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species)
occupant = H
if(!R.dna.real_name) //to prevent null names
R.dna.real_name = "clone ([rand(0,999)])"
H.real_name = R.dna.real_name
H.gender = R.gender
//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
H.adjustCloneLoss(150) // New damage var so you can't eject a clone early then stab them to abuse the current damage system --NeoFite
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)
//Here let's calculate their health so the pod doesn't immediately eject them!!!
H.updatehealth()
H.set_cloned_appearance()
// Move mind to body along with key
clonemind.transfer_to(H)
H.ckey = R.ckey
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?"))))
@@ -127,48 +137,29 @@
update_antag_icons(H.mind)
// -- End mode specific stuff
if(!R.dna)
H.dna = new /datum/dna()
qdel_swap(H.dna, new /datum/dna())
else
qdel_swap(H.dna, R.dna)
H.UpdateAppearance()
H.sync_dna_traits(FALSE) // Traitgenes Sync traits to genetics if needed
H.sync_organ_dna()
H.initialize_vessel()
H.set_cloned_appearance()
update_icon()
// 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
// Upgraded cloners can reduce the time of the modifier, up to 80%
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))
// Modifier that doesn't do anything.
H.add_modifier(/datum/modifier/cloned)
// This is really stupid.
for(var/modifier_type in R.genetic_modifiers)
H.add_modifier(modifier_type)
for(var/datum/language/L in R.languages)
H.add_language(L.name)
H.flavor_texts = R.flavor.Copy()
H.suiciding = 0
// 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
@@ -210,7 +201,7 @@
return
else if((!occupant) || (occupant.loc != src))
occupant = null
set_occupant(null)
if(locked)
locked = 0
return
@@ -219,6 +210,7 @@
//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
@@ -272,7 +264,7 @@
..()
/obj/machinery/clonepod/emag_act(var/remaining_charges, var/mob/user)
if(isnull(occupant))
if(isnull(get_occupant()))
return
to_chat(user, "You force an emergency ejection.")
locked = 0
@@ -301,7 +293,7 @@
heal_level = max(min((efficiency * 15) + 10, 100), MINIMUM_HEAL_LEVEL)
/obj/machinery/clonepod/proc/get_completion()
. = (100 * ((occupant.health + 100) / (heal_level + 100)))
. = (100 * ((get_occupant().health + 100) / (heal_level + 100)))
/obj/machinery/clonepod/verb/eject()
set name = "Eject Cloner"
@@ -324,20 +316,21 @@
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.loc = src.loc
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()
occupant = null
set_occupant(null)
update_icon()
return
@@ -400,6 +393,7 @@
return 0
/obj/machinery/clonepod/proc/malfunction()
var/mob/living/occupant = get_occupant()
if(occupant)
connected_message("Critical Error!")
mess = 1
@@ -421,21 +415,21 @@
switch(severity)
if(1.0)
for(var/atom/movable/A as mob|obj in src)
A.loc = src.loc
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.loc = src.loc
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.loc = src.loc
A.forceMove(get_turf(src))
ex_act(severity)
qdel(src)
return
@@ -444,7 +438,7 @@
/obj/machinery/clonepod/update_icon()
..()
icon_state = "pod_0"
if(occupant && !(stat & NOPOWER))
if(get_occupant() && !(stat & NOPOWER))
icon_state = "pod_1"
else if(mess)
icon_state = "pod_g"

View File

@@ -14,7 +14,7 @@
var/list/scantemp = null
var/menu = MENU_MAIN //Which menu screen to display
var/list/records = null
var/datum/dna2/record/active_record = null
var/datum/transhuman/body_record/active_BR = null
var/obj/item/disk/body_record/diskette = null // Traitgenes - Storing the entire body record
var/loading = 0 // Nice loading text
var/autoprocess = 0
@@ -34,27 +34,26 @@
/obj/machinery/computer/cloning/Destroy()
releasecloner()
for(var/datum/dna2/record/R in records)
qdel(R.dna)
qdel(R)
for(var/datum/transhuman/body_record/BR in records)
qdel(BR)
return ..()
/obj/machinery/computer/cloning/process()
if(!scanner || !pods.len || !autoprocess || stat & NOPOWER)
return
if(scanner.occupant && can_autoprocess())
scan_mob(scanner.occupant)
if(scanner.get_occupant() && can_autoprocess())
scan_mob(scanner.get_occupant())
if(!LAZYLEN(records))
return
for(var/obj/machinery/clonepod/pod in pods)
if(!(pod.occupant || pod.mess) && (pod.efficiency > 5))
for(var/datum/dna2/record/R in records)
if(!(pod.occupant || pod.mess))
if(pod.growclone(R))
records.Remove(R)
if(!(pod.get_occupant() || pod.mess) && (pod.efficiency > 5))
for(var/datum/transhuman/body_record/BR in records)
if(!(pod.get_occupant() || pod.mess))
if(pod.growclone(BR))
records.Remove(BR)
/obj/machinery/computer/cloning/proc/updatemodules()
scanner = findscanner()
@@ -67,8 +66,8 @@
var/obj/machinery/dna_scannernew/scannerf = null
//Try to find scanner on adjacent tiles first
for(dir in list(NORTH,EAST,SOUTH,WEST))
scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, dir))
for(var/scan_dir in list(NORTH,EAST,SOUTH,WEST))
scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, scan_dir))
if(scannerf)
return scannerf
@@ -97,7 +96,7 @@
if(istype(W, /obj/item/disk/body_record/)) //Traitgenes Storing the entire body record
if(!diskette)
user.drop_item()
W.loc = src
W.forceMove(src)
diskette = W
to_chat(user, "You insert [W].")
SStgui.update_uis(src)
@@ -154,17 +153,18 @@
if(pod.efficiency > 5)
canpodautoprocess = 1
var/mob/living/occupant = pod.get_occupant()
var/status = "idle"
if(pod.mess)
status = "mess"
else if(pod.occupant && !(pod.stat & NOPOWER))
else if(occupant && !(pod.stat & NOPOWER))
status = "cloning"
tempods.Add(list(list(
"pod" = "\ref[pod]",
"name" = sanitize(capitalize(pod.name)),
"biomass" = pod.get_biomass(),
"status" = status,
"progress" = (pod.occupant && pod.occupant.stat != DEAD) ? pod.get_completion() : 0
"progress" = (occupant && occupant.stat != DEAD) ? pod.get_completion() : 0
)))
data["pods"] = tempods
@@ -178,16 +178,16 @@
else
data["autoallowed"] = 0
if(scanner)
data["occupant"] = scanner.occupant
data["occupant"] = scanner.get_occupant()
data["locked"] = scanner.locked
data["temp"] = temp
data["scantemp"] = scantemp
data["disk"] = diskette
data["selected_pod"] = "\ref[selected_pod]"
var/list/temprecords[0]
for(var/datum/dna2/record/R in records)
var tempRealName = R.dna.real_name
temprecords.Add(list(list("record" = "\ref[R]", "realname" = sanitize(tempRealName))))
for(var/datum/transhuman/body_record/BR in records)
var tempRealName = BR.mydna.dna.real_name
temprecords.Add(list(list("record" = "\ref[BR]", "realname" = sanitize(tempRealName))))
data["records"] = temprecords
if(selected_pod && (selected_pod in pods) && selected_pod.get_biomass() >= CLONE_BIOMASS)
@@ -206,67 +206,68 @@
. = TRUE
switch(tgui_modal_act(src, action, params))
if(TGUI_MODAL_ANSWER)
if(params["id"] == "del_rec" && active_record)
if(params["id"] == "del_rec" && active_BR)
var/obj/item/card/id/C = ui.user.get_active_hand()
if(!istype(C) && !istype(C, /obj/item/pda))
set_temp("ID not in hand.", "danger")
return
if(check_access(C))
records.Remove(active_record)
qdel(active_record.dna)
qdel(active_record)
records.Remove(active_BR)
qdel(active_BR) // Already deletes dna in destroy()
set_temp("Record deleted.", "success")
menu = MENU_RECORDS
else
set_temp("Access denied.", "danger")
return
var/mob/living/carbon/human/scanner_occupant = scanner.get_occupant()
switch(action)
if("scan")
if(!scanner || !scanner.occupant || loading)
if(!scanner || !scanner_occupant || loading)
return
set_scan_temp("Scanner ready.", "good")
loading = TRUE
spawn(20)
if(can_brainscan() && scan_mode)
scan_mob(scanner.occupant, scan_brain = TRUE)
scan_mob(scanner_occupant, scan_brain = TRUE)
else
scan_mob(scanner.occupant)
scan_mob(scanner_occupant)
loading = FALSE
SStgui.update_uis(src)
if("autoprocess")
autoprocess = text2num(params["on"]) > 0
if("lock")
if(isnull(scanner) || !scanner.occupant) //No locking an open scanner.
if(isnull(scanner) || !scanner_occupant) //No locking an open scanner.
return
scanner.locked = !scanner.locked
if("view_rec")
var/ref = params["ref"]
if(!length(ref))
return
active_record = locate(ref)
if(istype(active_record))
if(isnull(active_record.ckey))
qdel(active_record)
active_BR = locate(ref)
if(istype(active_BR))
if(isnull(active_BR.ckey))
qdel(active_BR)
set_temp("Error: Record corrupt.", "danger")
else
var/obj/item/implant/health/H = null
if(active_record.implant)
H = locate(active_record.implant)
if(active_BR.mydna.implant)
H = locate(active_BR.mydna.implant)
var/list/payload = list(
activerecord = "\ref[active_record]",
activerecord = "\ref[active_BR]",
health = (H && istype(H)) ? H.sensehealth() : "",
realname = sanitize(active_record.dna.real_name),
unidentity = active_record.dna.uni_identity,
strucenzymes = active_record.dna.struc_enzymes,
realname = sanitize(active_BR.mydna.dna.real_name),
unidentity = active_BR.mydna.dna.uni_identity,
strucenzymes = active_BR.mydna.dna.struc_enzymes,
)
tgui_modal_message(src, action, "", null, payload)
else
active_record = null
active_BR = null
set_temp("Error: Record missing.", "danger")
if("del_rec")
if(!active_record)
if(!active_BR)
return
tgui_modal_boolean(src, action, "Please confirm that you want to delete the record by holding your ID and pressing Delete:", yes_text = "Delete", no_text = "Cancel")
if("disk") // Disk management.
@@ -277,24 +278,24 @@
if(isnull(diskette) || isnull(diskette.stored)) // Traitgenes Storing the entire body record
set_temp("Error: The disk's data could not be read.", "danger")
return
else if(isnull(active_record))
else if(isnull(active_BR))
set_temp("Error: No active record was found.", "danger")
menu = MENU_MAIN
return
active_record = diskette.stored.mydna // Traitgenes Storing the entire body record
active_BR = new(diskette.stored) // Traitgenes Storing the entire body record
set_temp("Successfully loaded from disk.", "success")
if("save")
if(isnull(diskette) || isnull(active_record)) // Traitgenes Removed readonly
if(isnull(diskette) || isnull(active_BR)) // Traitgenes Removed readonly
set_temp("Error: The data could not be saved.", "danger")
return
diskette.stored.mydna = active_record // Traitgenes Storing the entire body record
diskette.name = "data disk - '[active_record.dna.real_name]'"
diskette.stored = new(active_BR) // Traitgenes Storing the entire body record
diskette.name = "data disk - '[active_BR.mydna.dna.real_name]'"
set_temp("Successfully saved to disk.", "success")
if("eject")
if(!isnull(diskette))
diskette.loc = loc
diskette.forceMove(get_turf(src))
diskette = null
if("refresh")
SStgui.update_uis(src)
@@ -309,7 +310,7 @@
var/ref = params["ref"]
if(!length(ref))
return
var/datum/dna2/record/C = locate(ref)
var/datum/transhuman/body_record/C = locate(ref)
//Look for that player! They better be dead!
if(istype(C))
tgui_modal_clear(src)
@@ -321,7 +322,7 @@
var/cloneresult
if(!selected_pod)
set_temp("Error: No cloning pod selected.", "danger")
else if(pod.occupant)
else if(pod.get_occupant())
set_temp("Error: The cloning pod is currently occupied.", "danger")
else if(pod.get_biomass() < CLONE_BIOMASS)
set_temp("Error: Not enough biomass.", "danger")
@@ -335,7 +336,6 @@
set_temp("Initiating cloning cycle...", "success")
playsound(src, 'sound/machines/medbayscanner1.ogg', 100, 1)
records.Remove(C)
qdel(C.dna)
qdel(C)
menu = MENU_MAIN
else
@@ -407,49 +407,38 @@
return
for(var/obj/machinery/clonepod/pod in pods)
if(pod.occupant && pod.occupant.mind == subject.mind)
var/mob/living/occupant = pod.get_occupant()
if(occupant && occupant.mind == subject.mind)
set_scan_temp("Subject already getting cloned.")
SStgui.update_uis(src)
return
subject.dna.check_integrity()
var/datum/dna2/record/R = new /datum/dna2/record()
qdel_swap(R.dna, subject.dna)
R.ckey = subject.ckey
R.id = copytext(md5(subject.real_name), 2, 6)
R.name = R.dna.real_name
R.types = DNA2_BUF_UI|DNA2_BUF_UE|DNA2_BUF_SE
R.languages = subject.languages
R.gender = subject.gender
R.flavor = subject.flavor_texts.Copy()
for(var/datum/modifier/mod in subject.modifiers)
if(mod.flags & MODIFIER_GENETIC)
R.genetic_modifiers.Add(mod.type)
var/datum/transhuman/body_record/BR = new(subject)
//Add an implant if needed
var/obj/item/implant/health/imp = locate(/obj/item/implant/health, subject)
if (isnull(imp))
imp = new /obj/item/implant/health(subject)
imp.implanted = subject
R.implant = "\ref[imp]"
BR.mydna.implant = "\ref[imp]"
//Update it if needed
else
R.implant = "\ref[imp]"
BR.mydna.implant = "\ref[imp]"
if (!isnull(subject.mind)) //Save that mind so traitors can continue traitoring after cloning.
R.mind = "\ref[subject.mind]"
BR.mydna.mind = "\ref[subject.mind]"
records += R
records += BR
set_scan_temp("Subject successfully scanned.", "good")
SStgui.update_uis(src)
//Find a specific record by key.
/obj/machinery/computer/cloning/proc/find_record(var/find_key)
var/selected_record = null
for(var/datum/dna2/record/R in records)
if(R.ckey == find_key)
selected_record = R
for(var/datum/transhuman/body_record/BR in records)
if(BR.mydna.ckey == find_key)
selected_record = BR
break
return selected_record

View File

@@ -271,6 +271,8 @@
if(def_lang)
P.default_language = def_lang
SEND_SIGNAL(P, COMSIG_HUMAN_DNA_FINALIZED)
protean_brain.brainmob.mind.transfer_to(P)
protean_brain.loc = BR
protean_refactory = null

View File

@@ -135,6 +135,8 @@
avatar.sync_organ_dna()
avatar.initialize_vessel()
SEND_SIGNAL(avatar, COMSIG_HUMAN_DNA_FINALIZED)
var/newname = sanitize(tgui_input_text(avatar, "Your mind feels foggy. You're certain your name is [occupant.real_name], but it could also be [avatar.name]. Would you like to change it to something else?", "Name change", null, MAX_NAME_LEN), MAX_NAME_LEN)
if (newname)
avatar.real_name = newname

View File

@@ -314,6 +314,8 @@
avatar.sync_organ_dna()
avatar.initialize_vessel()
SEND_SIGNAL(avatar, COMSIG_HUMAN_DNA_FINALIZED)
if(tf)
var/mob/living/new_form = avatar.transform_into_mob(tf, TRUE) // No need to check prefs when the occupant already chose to transform.
if(isliving(new_form)) // Make sure the mob spawned properly.

View File

@@ -106,6 +106,8 @@
if(is_lang_whitelisted(usr,chosen_language) || (avatar.species && (chosen_language.name in avatar.species.secondary_langs)))
avatar.add_language(lang)
SEND_SIGNAL(avatar, COMSIG_HUMAN_DNA_FINALIZED)
avatar.regenerate_icons()
avatar.update_transform()
job_master.EquipRank(avatar,JOB_VR, 1, FALSE)