Files
Bubberstation/code/datums/dna.dm
Time-Green 4fd0e28f42 Refactors body markings into bodypart overlays (#83744)
## About The Pull Request
Refactors body markings (lizard and moths) into the bodypart overlays
system
I've also renamed the icon states for lizard bodymarkings (downstreams
with lots of lizard body markings might have problems with this)

## Why It's Good For The Game
Gives us better controle over /human appearances. I didn't add any means
of actually changing them on the person (aside from genetics. how would
that even work? with a waterproof marker?), but makes it easy for
when/if I do. Also unloads mutant_bodyparts more, moving is yet closer
to full species dedatumization

## Changelog
🆑
refactor: Lizard and moth markings now use the bodypart overlay system
/🆑
2024-06-18 14:07:47 +02:00

959 lines
42 KiB
Plaintext

/**
* Some identity blocks (basically pieces of the unique_identity string variable of the dna datum, commonly abbreviated with ui)
* may have a length that differ from standard length of 3 ASCII characters. This list is necessary
* for these non-standard blocks to work, as well as the entire unique identity string.
* Should you add a new ui block which size differ from the standard (again, 3 ASCII characters), like for example, a color,
* please do not forget to also include it in this list in the following format:
* "[dna block number]" = dna block size,
* Failure to do that may result in bugs. Thanks.
*/
GLOBAL_LIST_INIT(identity_block_lengths, list(
"[DNA_HAIR_COLOR_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
"[DNA_FACIAL_HAIR_COLOR_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
"[DNA_EYE_COLOR_LEFT_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
"[DNA_EYE_COLOR_RIGHT_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
"[DNA_HAIR_COLOR_GRADIENT_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
"[DNA_FACIAL_HAIR_COLOR_GRADIENT_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
))
/**
* The same rules of the above also apply here, with the exception that this is for the unique_features string variable
* (commonly abbreviated with uf) and its blocks. Both ui and uf have a standard block length of 3 ASCII characters.
*/
GLOBAL_LIST_INIT(features_block_lengths, list(
"[DNA_MUTANT_COLOR_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
"[DNA_ETHEREAL_COLOR_BLOCK]" = DNA_BLOCK_SIZE_COLOR,
))
/**
* A list of numbers that keeps track of where ui blocks start in the unique_identity string variable of the dna datum.
* Commonly used by the datum/dna/set_uni_identity_block and datum/dna/get_uni_identity_block procs.
*/
GLOBAL_LIST_INIT(total_ui_len_by_block, populate_total_ui_len_by_block())
/proc/populate_total_ui_len_by_block()
. = list()
var/total_block_len = 1
for(var/blocknumber in 1 to DNA_UNI_IDENTITY_BLOCKS)
. += total_block_len
total_block_len += GET_UI_BLOCK_LEN(blocknumber)
///Ditto but for unique features. Used by the datum/dna/set_uni_feature_block and datum/dna/get_uni_feature_block procs.
GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
/proc/populate_total_uf_len_by_block()
. = list()
var/total_block_len = 1
for(var/blocknumber in 1 to DNA_FEATURE_BLOCKS)
. += total_block_len
total_block_len += GET_UF_BLOCK_LEN(blocknumber)
/////////////////////////// DNA DATUM
/datum/dna
///An md5 hash of the dna holder's real name
var/unique_enzymes
///Stores the hashed values of traits such as skin tones, hair style, and gender
var/unique_identity
var/blood_type
///The type of mutant race the player is if applicable (i.e. potato-man)
var/datum/species/species = new /datum/species/human
/// Assoc list of feature keys to their value
/// Note if you set these manually, and do not update [unique_features] afterwards, it will likely be reset.
var/list/features = list("mcolor" = COLOR_WHITE)
///Stores the hashed values of the person's non-human features
var/unique_features
///Stores the real name of the person who originally got this dna datum. Used primarely for changelings,
var/real_name
///All mutations are from now on here
var/list/mutations = list()
///Temporary changes to the UE
var/list/temporary_mutations = list()
///For temporary name/ui/ue/blood_type modifications
var/list/previous = list()
var/mob/living/holder
///List of which mutations this carbon has and its assigned block
var/mutation_index[DNA_MUTATION_BLOCKS]
///List of the default genes from this mutation to allow DNA Scanner highlighting
var/default_mutation_genes[DNA_MUTATION_BLOCKS]
var/stability = 100
///Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers.
var/scrambled = FALSE
/datum/dna/New(mob/living/new_holder)
if(istype(new_holder))
holder = new_holder
/datum/dna/Destroy()
if(iscarbon(holder))
var/mob/living/carbon/cholder = holder
remove_all_mutations() // mutations hold a reference to the dna
if(cholder.dna == src)
cholder.dna = null
holder = null
QDEL_NULL(species)
mutations.Cut() //This only references mutations, just dereference.
temporary_mutations.Cut() //^
previous.Cut() //^
return ..()
/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = FALSE, transfer_species = TRUE)
if(!istype(destination))
return
destination.dna.unique_enzymes = unique_enzymes
destination.dna.unique_identity = unique_identity
destination.dna.blood_type = blood_type
destination.dna.unique_features = unique_features
destination.dna.features = features.Copy()
destination.dna.real_name = real_name
destination.dna.temporary_mutations = temporary_mutations.Copy()
if(transfer_SE)
destination.dna.mutation_index = mutation_index
destination.dna.default_mutation_genes = default_mutation_genes
if(transfer_species)
destination.set_species(species.type, icon_update=0)
/datum/dna/proc/copy_dna(datum/dna/new_dna)
new_dna.unique_enzymes = unique_enzymes
new_dna.mutation_index = mutation_index
new_dna.default_mutation_genes = default_mutation_genes
new_dna.unique_identity = unique_identity
new_dna.unique_features = unique_features
new_dna.blood_type = blood_type
new_dna.features = features.Copy()
//if the new DNA has a holder, transform them immediately, otherwise save it
if(new_dna.holder)
new_dna.holder.set_species(species.type, icon_update = 0)
else
new_dna.species = new species.type
new_dna.real_name = real_name
// Mutations aren't gc managed, but they still aren't templates
// Let's do a proper copy
for(var/datum/mutation/human/mutation in mutations)
new_dna.add_mutation(mutation, mutation.class, mutation.timeout)
//See mutation.dm for what 'class' does. 'time' is time till it removes itself in decimals. 0 for no timer
/datum/dna/proc/add_mutation(mutation, class = MUT_OTHER, time)
var/mutation_type = mutation
if(istype(mutation, /datum/mutation/human))
var/datum/mutation/human/HM = mutation
mutation_type = HM.type
if(get_mutation(mutation_type))
return
SEND_SIGNAL(holder, COMSIG_CARBON_GAIN_MUTATION, mutation_type, class)
return force_give(new mutation_type (class, time, copymut = mutation))
/datum/dna/proc/remove_mutation(mutation_type)
SEND_SIGNAL(holder, COMSIG_CARBON_LOSE_MUTATION, mutation_type)
return force_lose(get_mutation(mutation_type))
/datum/dna/proc/check_mutation(mutation_type)
return get_mutation(mutation_type)
/datum/dna/proc/remove_all_mutations(list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE)
remove_mutation_group(mutations, classes, mutadone)
scrambled = FALSE
/datum/dna/proc/remove_mutation_group(list/group, list/classes = list(MUT_NORMAL, MUT_EXTRA, MUT_OTHER), mutadone = FALSE)
if(!group)
return
for(var/datum/mutation/human/HM in group)
if((HM.class in classes) && !(HM.mutadone_proof && mutadone))
remove_mutation(HM)
/datum/dna/proc/generate_unique_identity()
. = ""
var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS)
//ignores TRAIT_AGENDER so that a "real" gender can be stored in the DNA if later use is needed
switch(holder.gender)
if(MALE)
L[DNA_GENDER_BLOCK] = construct_block(G_MALE, GENDERS)
if(FEMALE)
L[DNA_GENDER_BLOCK] = construct_block(G_FEMALE, GENDERS)
if(NEUTER)
L[DNA_GENDER_BLOCK] = construct_block(G_NEUTER, GENDERS)
else
L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, GENDERS)
if(ishuman(holder))
var/mob/living/carbon/human/H = holder
if(length(SSaccessories.hairstyles_list) == 0 || length(SSaccessories.facial_hairstyles_list) == 0)
CRASH("SSaccessories lists are empty, this is bad!")
L[DNA_HAIRSTYLE_BLOCK] = construct_block(SSaccessories.hairstyles_list.Find(H.hairstyle), length(SSaccessories.hairstyles_list))
L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color, include_crunch = FALSE)
L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(SSaccessories.facial_hairstyles_list.Find(H.facial_hairstyle), length(SSaccessories.facial_hairstyles_list))
L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color, include_crunch = FALSE)
L[DNA_SKIN_TONE_BLOCK] = construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len)
L[DNA_EYE_COLOR_LEFT_BLOCK] = sanitize_hexcolor(H.eye_color_left, include_crunch = FALSE)
L[DNA_EYE_COLOR_RIGHT_BLOCK] = sanitize_hexcolor(H.eye_color_right, include_crunch = FALSE)
L[DNA_HAIRSTYLE_GRADIENT_BLOCK] = construct_block(SSaccessories.hair_gradients_list.Find(H.grad_style[GRADIENT_HAIR_KEY]), length(SSaccessories.hair_gradients_list))
L[DNA_HAIR_COLOR_GRADIENT_BLOCK] = sanitize_hexcolor(H.grad_color[GRADIENT_HAIR_KEY], include_crunch = FALSE)
L[DNA_FACIAL_HAIRSTYLE_GRADIENT_BLOCK] = construct_block(SSaccessories.facial_hair_gradients_list.Find(H.grad_style[GRADIENT_FACIAL_HAIR_KEY]), length(SSaccessories.facial_hair_gradients_list))
L[DNA_FACIAL_HAIR_COLOR_GRADIENT_BLOCK] = sanitize_hexcolor(H.grad_color[GRADIENT_FACIAL_HAIR_KEY], include_crunch = FALSE)
for(var/blocknum in 1 to DNA_UNI_IDENTITY_BLOCKS)
. += L[blocknum] || random_string(GET_UI_BLOCK_LEN(blocknum), GLOB.hex_characters)
/datum/dna/proc/generate_unique_features()
. = ""
var/list/L = new /list(DNA_FEATURE_BLOCKS)
if(features["mcolor"])
L[DNA_MUTANT_COLOR_BLOCK] = sanitize_hexcolor(features["mcolor"], include_crunch = FALSE)
if(features["ethcolor"])
L[DNA_ETHEREAL_COLOR_BLOCK] = sanitize_hexcolor(features["ethcolor"], include_crunch = FALSE)
if(features["lizard_markings"])
L[DNA_LIZARD_MARKINGS_BLOCK] = construct_block(SSaccessories.lizard_markings_list.Find(features["lizard_markings"]), length(SSaccessories.lizard_markings_list))
if(features["tail_cat"])
L[DNA_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_human.Find(features["tail_cat"]), length(SSaccessories.tails_list_human))
if(features["tail_lizard"])
L[DNA_LIZARD_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_lizard.Find(features["tail_lizard"]), length(SSaccessories.tails_list_lizard))
if(features["tail_monkey"])
L[DNA_MONKEY_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_monkey.Find(features["tail_monkey"]), length(SSaccessories.tails_list_monkey))
if(features["snout"])
L[DNA_SNOUT_BLOCK] = construct_block(SSaccessories.snouts_list.Find(features["snout"]), length(SSaccessories.snouts_list))
if(features["horns"])
L[DNA_HORNS_BLOCK] = construct_block(SSaccessories.horns_list.Find(features["horns"]), length(SSaccessories.horns_list))
if(features["frills"])
L[DNA_FRILLS_BLOCK] = construct_block(SSaccessories.frills_list.Find(features["frills"]), length(SSaccessories.frills_list))
if(features["spines"])
L[DNA_SPINES_BLOCK] = construct_block(SSaccessories.spines_list.Find(features["spines"]), length(SSaccessories.spines_list))
if(features["ears"])
L[DNA_EARS_BLOCK] = construct_block(SSaccessories.ears_list.Find(features["ears"]), length(SSaccessories.ears_list))
if(features["moth_wings"] != "Burnt Off")
L[DNA_MOTH_WINGS_BLOCK] = construct_block(SSaccessories.moth_wings_list.Find(features["moth_wings"]), length(SSaccessories.moth_wings_list))
if(features["moth_antennae"] != "Burnt Off")
L[DNA_MOTH_ANTENNAE_BLOCK] = construct_block(SSaccessories.moth_antennae_list.Find(features["moth_antennae"]), length(SSaccessories.moth_antennae_list))
if(features["moth_markings"])
L[DNA_MOTH_MARKINGS_BLOCK] = construct_block(SSaccessories.moth_markings_list.Find(features["moth_markings"]), length(SSaccessories.moth_markings_list))
if(features["caps"])
L[DNA_MUSHROOM_CAPS_BLOCK] = construct_block(SSaccessories.caps_list.Find(features["caps"]), length(SSaccessories.caps_list))
if(features["pod_hair"])
L[DNA_POD_HAIR_BLOCK] = construct_block(SSaccessories.pod_hair_list.Find(features["pod_hair"]), length(SSaccessories.pod_hair_list))
for(var/blocknum in 1 to DNA_FEATURE_BLOCKS)
. += L[blocknum] || random_string(GET_UI_BLOCK_LEN(blocknum), GLOB.hex_characters)
/datum/dna/proc/generate_dna_blocks()
var/bonus
if(species?.inert_mutation)
bonus = GET_INITIALIZED_MUTATION(species.inert_mutation)
var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus
if(!LAZYLEN(mutations_temp))
return
mutation_index.Cut()
default_mutation_genes.Cut()
shuffle_inplace(mutations_temp)
mutation_index[/datum/mutation/human/race] = create_sequence(/datum/mutation/human/race, FALSE)
default_mutation_genes[/datum/mutation/human/race] = mutation_index[/datum/mutation/human/race]
for(var/i in 2 to DNA_MUTATION_BLOCKS)
var/datum/mutation/human/M = mutations_temp[i]
mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty)
default_mutation_genes[M.type] = mutation_index[M.type]
shuffle_inplace(mutation_index)
//Used to generate original gene sequences for every mutation
/proc/generate_gene_sequence(length=4)
var/static/list/active_sequences = list("AT","TA","GC","CG")
var/sequence
for(var/i in 1 to length*DNA_SEQUENCE_LENGTH)
sequence += pick(active_sequences)
return sequence
//Used to create a chipped gene sequence
/proc/create_sequence(mutation, active, difficulty)
if(!difficulty)
var/datum/mutation/human/A = GET_INITIALIZED_MUTATION(mutation) //leaves the possibility to change difficulty mid-round
if(!A)
return
difficulty = A.difficulty
difficulty += rand(-2,4)
var/sequence = GET_SEQUENCE(mutation)
if(active)
return sequence
while(difficulty)
var/randnum = rand(1, length(sequence))
sequence = copytext(sequence, 1, randnum) + "X" + copytext(sequence, randnum + 1)
difficulty--
return sequence
/datum/dna/proc/generate_unique_enzymes()
. = ""
if(istype(holder))
real_name = holder.real_name
. += md5(holder.real_name)
else
. += random_string(DNA_UNIQUE_ENZYMES_LEN, GLOB.hex_characters)
return .
///Setter macro used to modify unique identity blocks.
/datum/dna/proc/set_uni_identity_block(blocknum, input)
var/precesing_blocks = copytext(unique_identity, 1, GLOB.total_ui_len_by_block[blocknum])
var/succeeding_blocks = blocknum < GLOB.total_ui_len_by_block.len ? copytext(unique_identity, GLOB.total_ui_len_by_block[blocknum+1]) : ""
unique_identity = precesing_blocks + input + succeeding_blocks
///Setter macro used to modify unique features blocks.
/datum/dna/proc/set_uni_feature_block(blocknum, input)
var/precesing_blocks = copytext(unique_features, 1, GLOB.total_uf_len_by_block[blocknum])
var/succeeding_blocks = blocknum < GLOB.total_uf_len_by_block.len ? copytext(unique_features, GLOB.total_uf_len_by_block[blocknum+1]) : ""
unique_features = precesing_blocks + input + succeeding_blocks
/datum/dna/proc/update_ui_block(blocknumber)
if(!blocknumber)
CRASH("UI block index is null")
if(!ishuman(holder))
CRASH("Non-human mobs shouldn't have DNA")
var/mob/living/carbon/human/H = holder
switch(blocknumber)
if(DNA_HAIR_COLOR_BLOCK)
set_uni_identity_block(blocknumber, sanitize_hexcolor(H.hair_color, include_crunch = FALSE))
if(DNA_FACIAL_HAIR_COLOR_BLOCK)
set_uni_identity_block(blocknumber, sanitize_hexcolor(H.facial_hair_color, include_crunch = FALSE))
if(DNA_SKIN_TONE_BLOCK)
set_uni_identity_block(blocknumber, construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len))
if(DNA_EYE_COLOR_LEFT_BLOCK)
set_uni_identity_block(blocknumber, sanitize_hexcolor(H.eye_color_left, include_crunch = FALSE))
if(DNA_EYE_COLOR_RIGHT_BLOCK)
set_uni_identity_block(blocknumber, sanitize_hexcolor(H.eye_color_right, include_crunch = FALSE))
if(DNA_GENDER_BLOCK)
switch(H.gender)
if(MALE)
set_uni_identity_block(blocknumber, construct_block(G_MALE, GENDERS))
if(FEMALE)
set_uni_identity_block(blocknumber, construct_block(G_FEMALE, GENDERS))
if(NEUTER)
set_uni_identity_block(blocknumber, construct_block(G_NEUTER, GENDERS))
else
set_uni_identity_block(blocknumber, construct_block(G_PLURAL, GENDERS))
if(DNA_FACIAL_HAIRSTYLE_BLOCK)
set_uni_identity_block(blocknumber, construct_block(SSaccessories.facial_hairstyles_list.Find(H.facial_hairstyle), length(SSaccessories.facial_hairstyles_list)))
if(DNA_HAIRSTYLE_BLOCK)
set_uni_identity_block(blocknumber, construct_block(SSaccessories.hairstyles_list.Find(H.hairstyle), length(SSaccessories.hairstyles_list)))
if(DNA_HAIRSTYLE_GRADIENT_BLOCK)
set_uni_identity_block(blocknumber, construct_block(SSaccessories.hair_gradients_list.Find(H.grad_style[GRADIENT_HAIR_KEY]), length(SSaccessories.hair_gradients_list)))
if(DNA_FACIAL_HAIRSTYLE_GRADIENT_BLOCK)
set_uni_identity_block(blocknumber, construct_block(SSaccessories.facial_hair_gradients_list.Find(H.grad_style[GRADIENT_FACIAL_HAIR_KEY]), length(SSaccessories.facial_hair_gradients_list)))
if(DNA_HAIR_COLOR_GRADIENT_BLOCK)
set_uni_identity_block(blocknumber, sanitize_hexcolor(H.grad_color[GRADIENT_HAIR_KEY], include_crunch = FALSE))
if(DNA_FACIAL_HAIR_COLOR_GRADIENT_BLOCK)
set_uni_identity_block(blocknumber, sanitize_hexcolor(H.grad_color[GRADIENT_FACIAL_HAIR_KEY], include_crunch = FALSE))
/datum/dna/proc/update_uf_block(blocknumber)
if(!blocknumber)
CRASH("UF block index is null")
if(!ishuman(holder))
CRASH("Non-human mobs shouldn't have DNA")
switch(blocknumber)
if(DNA_MUTANT_COLOR_BLOCK)
set_uni_feature_block(blocknumber, sanitize_hexcolor(features["mcolor"], include_crunch = FALSE))
if(DNA_ETHEREAL_COLOR_BLOCK)
set_uni_feature_block(blocknumber, sanitize_hexcolor(features["ethcolor"], include_crunch = FALSE))
if(DNA_LIZARD_MARKINGS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.lizard_markings_list.Find(features["lizard_markings"]), length(SSaccessories.lizard_markings_list)))
if(DNA_TAIL_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_human.Find(features["tail_cat"]), length(SSaccessories.tails_list_human)))
if(DNA_LIZARD_TAIL_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_lizard.Find(features["tail_lizard"]), length(SSaccessories.tails_list_lizard)))
if(DNA_MONKEY_TAIL_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_monkey.Find(features["tail_monkey"]), length(SSaccessories.tails_list_monkey)))
if(DNA_SNOUT_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.snouts_list.Find(features["snout"]), length(SSaccessories.snouts_list)))
if(DNA_HORNS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.horns_list.Find(features["horns"]), length(SSaccessories.horns_list)))
if(DNA_FRILLS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.frills_list.Find(features["frills"]), length(SSaccessories.frills_list)))
if(DNA_SPINES_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.spines_list.Find(features["spines"]), length(SSaccessories.spines_list)))
if(DNA_EARS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.ears_list.Find(features["ears"]), length(SSaccessories.ears_list)))
if(DNA_MOTH_WINGS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.moth_wings_list.Find(features["moth_wings"]), length(SSaccessories.moth_wings_list)))
if(DNA_MOTH_ANTENNAE_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.moth_antennae_list.Find(features["moth_antennae"]), length(SSaccessories.moth_antennae_list)))
if(DNA_MOTH_MARKINGS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.moth_markings_list.Find(features["moth_markings"]), length(SSaccessories.moth_markings_list)))
if(DNA_MUSHROOM_CAPS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.caps_list.Find(features["caps"]), length(SSaccessories.caps_list)))
if(DNA_POD_HAIR_BLOCK)
set_uni_feature_block(blocknumber, construct_block(SSaccessories.pod_hair_list.Find(features["pod_hair"]), length(SSaccessories.pod_hair_list)))
//Please use add_mutation or activate_mutation instead
/datum/dna/proc/force_give(datum/mutation/human/human_mutation)
if(holder && human_mutation)
if(human_mutation.class == MUT_NORMAL)
set_se(1, human_mutation)
. = human_mutation.on_acquiring(holder)
if(.)
qdel(human_mutation)
update_instability()
//Use remove_mutation instead
/datum/dna/proc/force_lose(datum/mutation/human/human_mutation)
if(holder && (human_mutation in mutations))
set_se(0, human_mutation)
. = human_mutation.on_losing(holder)
qdel(human_mutation) // qdel mutations on removal
update_instability(FALSE)
return
/**
* Checks if two DNAs are practically the same by comparing their most defining features
*
* Arguments:
* * target_dna The DNA that we are comparing to
*/
/datum/dna/proc/is_same_as(datum/dna/target_dna)
if( \
unique_identity == target_dna.unique_identity \
&& mutation_index == target_dna.mutation_index \
&& real_name == target_dna.real_name \
&& species.type == target_dna.species.type \
&& compare_list(features, target_dna.features) \
&& blood_type == target_dna.blood_type \
)
return TRUE
return FALSE
/datum/dna/proc/update_instability(alert=TRUE)
stability = 100
for(var/datum/mutation/human/M in mutations)
if(M.class == MUT_EXTRA || M.instability < 0)
stability -= M.instability * GET_MUTATION_STABILIZER(M)
if(holder)
var/message
if(alert)
switch(stability)
if(70 to 90)
message = span_warning("You shiver.")
if(60 to 69)
message = span_warning("You feel cold.")
if(40 to 59)
message = span_warning("You feel sick.")
if(20 to 39)
message = span_warning("It feels like your skin is moving.")
if(1 to 19)
message = span_warning("You can feel your cells burning.")
if(-INFINITY to 0)
message = span_boldwarning("You can feel your DNA exploding, we need to do something fast!")
if(stability <= 0)
holder.apply_status_effect(/datum/status_effect/dna_melt)
if(message)
to_chat(holder, message)
/// Updates the UI, UE, and UF of the DNA according to the features, appearance, name, etc. of the DNA / holder.
/datum/dna/proc/update_dna_identity()
unique_identity = generate_unique_identity()
unique_enzymes = generate_unique_enzymes()
unique_features = generate_unique_features()
/**
* Sets up DNA codes and initializes some features.
*
* * newblood_type - Optional, the blood type to set the DNA to
* * create_mutation_blocks - If true, generate_dna_blocks is called, which is used to set up mutation blocks (what a mob can naturally mutate).
* * randomize_features - If true, all entries in the features list will be randomized.
*/
/datum/dna/proc/initialize_dna(newblood_type, create_mutation_blocks = TRUE, randomize_features = TRUE)
if(newblood_type)
blood_type = newblood_type
if(create_mutation_blocks) //I hate this
generate_dna_blocks()
if(randomize_features)
for(var/species_type in GLOB.species_prototypes)
features |= GLOB.species_prototypes[species_type].randomize_features()
features["mcolor"] = "#[random_color()]"
update_dna_identity()
/datum/dna/stored //subtype used by brain mob's stored_dna and the crew manifest
/datum/dna/stored/add_mutation(mutation_name) //no mutation changes on stored dna.
return
/datum/dna/stored/remove_mutation(mutation_name)
return
/datum/dna/stored/check_mutation(mutation_name)
return
/datum/dna/stored/remove_all_mutations(list/classes, mutadone = FALSE)
return
/datum/dna/stored/remove_mutation_group(list/group)
return
/////////////////////////// DNA MOB-PROCS //////////////////////
/mob/proc/set_species(datum/species/mrace, icon_update = 1)
SHOULD_NOT_SLEEP(TRUE)
return
/mob/living/brain/set_species(datum/species/mrace, icon_update = 1)
if(mrace)
if(ispath(mrace))
stored_dna.species = new mrace()
else
stored_dna.species = mrace //not calling any species update procs since we're a brain, not a monkey/human
/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE)
if(QDELETED(src))
CRASH("You're trying to change your species post deletion, this is a recipe for madness")
if(isnull(mrace))
CRASH("set_species called without a species to set to")
if(!has_dna())
return
var/datum/species/new_race
if(ispath(mrace))
new_race = new mrace
else if(istype(mrace))
if(QDELING(mrace))
CRASH("someone is calling set_species() and is passing it a qdeling species datum, this is VERY bad, stop it")
new_race = mrace
else
CRASH("set_species called with an invalid mrace [mrace]")
death_sound = new_race.death_sound
var/datum/species/old_species = dna.species
dna.species = new_race
if (old_species.properly_gained)
old_species.on_species_loss(src, new_race, pref_load)
dna.species.on_species_gain(src, old_species, pref_load)
log_mob_tag("TAG: [tag] SPECIES: [key_name(src)] \[[mrace]\]")
/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE)
..()
if(icon_update)
update_body(is_creating = TRUE)
update_mutations_overlay()// no lizard with human hulk overlay please.
/mob/proc/has_dna()
return
/mob/living/carbon/has_dna()
return dna
/// Returns TRUE if the mob is allowed to mutate via its DNA, or FALSE if otherwise.
/// Only an organic Carbon with valid DNA may mutate; not robots, AIs, aliens, Ians, or other mobs.
/mob/proc/can_mutate()
return FALSE
/mob/living/carbon/can_mutate()
if(!(mob_biotypes & MOB_ORGANIC))
return FALSE
if(has_dna() && !HAS_TRAIT(src, TRAIT_GENELESS) && !HAS_TRAIT(src, TRAIT_BADDNA))
return TRUE
/// Sets the DNA of the mob to the given DNA.
/mob/living/carbon/human/proc/hardset_dna(unique_identity, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations)
//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc)
if(newfeatures)
dna.features = newfeatures
dna.generate_unique_features()
if(mrace)
var/datum/species/newrace = new mrace.type
newrace.copy_properties_from(mrace)
set_species(newrace, icon_update=0)
if(newreal_name)
dna.real_name = newreal_name
dna.generate_unique_enzymes()
if(newblood_type)
dna.blood_type = newblood_type
if(unique_identity)
dna.unique_identity = unique_identity
updateappearance(icon_update = 0)
if(LAZYLEN(mutation_index))
dna.mutation_index = mutation_index.Copy()
if(LAZYLEN(default_mutation_genes))
dna.default_mutation_genes = default_mutation_genes.Copy()
else
dna.default_mutation_genes = mutation_index.Copy()
domutcheck()
if(mrace || newfeatures || unique_identity)
update_body(is_creating = TRUE)
update_mutations_overlay()
if(LAZYLEN(mutations) && force_transfer_mutations)
for(var/datum/mutation/human/mutation as anything in mutations)
dna.force_give(new mutation.type(mutation.class, copymut = mutation)) //using force_give since it may include exotic mutations that otherwise won't be handled properly
/mob/living/carbon/proc/create_dna()
dna = new /datum/dna(src)
if(!dna.species)
var/rando_race = pick(get_selectable_species())
dna.species = new rando_race()
//proc used to update the mob's appearance after its dna UI has been changed
/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0)
if(!has_dna())
return
//Always plural gender if agender
if(HAS_TRAIT(src, TRAIT_AGENDER))
gender = PLURAL
return
switch(deconstruct_block(get_uni_identity_block(dna.unique_identity, DNA_GENDER_BLOCK), GENDERS))
if(G_MALE)
gender = MALE
if(G_FEMALE)
gender = FEMALE
if(G_NEUTER)
gender = NEUTER
else
gender = PLURAL
/mob/living/carbon/human/updateappearance(icon_update = TRUE, mutcolor_update = FALSE, mutations_overlay_update = FALSE)
..()
var/structure = dna.unique_identity
skin_tone = GLOB.skin_tones[deconstruct_block(get_uni_identity_block(structure, DNA_SKIN_TONE_BLOCK), GLOB.skin_tones.len)]
eye_color_left = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_LEFT_BLOCK))
eye_color_right = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_RIGHT_BLOCK))
set_haircolor(sanitize_hexcolor(get_uni_identity_block(structure, DNA_HAIR_COLOR_BLOCK)), update = FALSE)
set_facial_haircolor(sanitize_hexcolor(get_uni_identity_block(structure, DNA_FACIAL_HAIR_COLOR_BLOCK)), update = FALSE)
set_hair_gradient_color(sanitize_hexcolor(get_uni_identity_block(structure, DNA_HAIR_COLOR_GRADIENT_BLOCK)), update = FALSE)
set_facial_hair_gradient_color(sanitize_hexcolor(get_uni_identity_block(structure, DNA_FACIAL_HAIR_COLOR_GRADIENT_BLOCK)), update = FALSE)
if(HAS_TRAIT(src, TRAIT_SHAVED))
set_facial_hairstyle("Shaved", update = FALSE)
else
var/style = SSaccessories.facial_hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), length(SSaccessories.facial_hairstyles_list))]
var/gradient_style = SSaccessories.facial_hair_gradients_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIRSTYLE_GRADIENT_BLOCK), length(SSaccessories.facial_hair_gradients_list))]
set_facial_hairstyle(style, update = FALSE)
set_facial_hair_gradient_style(gradient_style, update = FALSE)
if(HAS_TRAIT(src, TRAIT_BALD))
set_hairstyle("Bald", update = FALSE)
else
var/style = SSaccessories.hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_HAIRSTYLE_BLOCK), length(SSaccessories.hairstyles_list))]
var/gradient_style = SSaccessories.hair_gradients_list[deconstruct_block(get_uni_identity_block(structure, DNA_HAIRSTYLE_GRADIENT_BLOCK), length(SSaccessories.hair_gradients_list))]
set_hairstyle(style, update = FALSE)
set_hair_gradient_style(gradient_style, update = FALSE)
var/features = dna.unique_features
if(dna.features["mcolor"])
dna.features["mcolor"] = sanitize_hexcolor(get_uni_feature_block(features, DNA_MUTANT_COLOR_BLOCK))
if(dna.features["ethcolor"])
dna.features["ethcolor"] = sanitize_hexcolor(get_uni_feature_block(features, DNA_ETHEREAL_COLOR_BLOCK))
if(dna.features["lizard_markings"])
dna.features["lizard_markings"] = SSaccessories.lizard_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_MARKINGS_BLOCK), length(SSaccessories.lizard_markings_list))]
if(dna.features["snout"])
dna.features["snout"] = SSaccessories.snouts_list[deconstruct_block(get_uni_feature_block(features, DNA_SNOUT_BLOCK), length(SSaccessories.snouts_list))]
if(dna.features["horns"])
dna.features["horns"] = SSaccessories.horns_list[deconstruct_block(get_uni_feature_block(features, DNA_HORNS_BLOCK), length(SSaccessories.horns_list))]
if(dna.features["frills"])
dna.features["frills"] = SSaccessories.frills_list[deconstruct_block(get_uni_feature_block(features, DNA_FRILLS_BLOCK), length(SSaccessories.frills_list))]
if(dna.features["spines"])
dna.features["spines"] = SSaccessories.spines_list[deconstruct_block(get_uni_feature_block(features, DNA_SPINES_BLOCK), length(SSaccessories.spines_list))]
if(dna.features["tail_cat"])
dna.features["tail_cat"] = SSaccessories.tails_list_human[deconstruct_block(get_uni_feature_block(features, DNA_TAIL_BLOCK), length(SSaccessories.tails_list_human))]
if(dna.features["tail_lizard"])
dna.features["tail_lizard"] = SSaccessories.tails_list_lizard[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_TAIL_BLOCK), length(SSaccessories.tails_list_lizard))]
if(dna.features["tail_monkey"])
dna.features["tail_monkey"] = SSaccessories.tails_list_monkey[deconstruct_block(get_uni_feature_block(features, DNA_MONKEY_TAIL_BLOCK), length(SSaccessories.tails_list_monkey))]
if(dna.features["ears"])
dna.features["ears"] = SSaccessories.ears_list[deconstruct_block(get_uni_feature_block(features, DNA_EARS_BLOCK), length(SSaccessories.ears_list))]
if(dna.features["moth_wings"])
var/genetic_value = SSaccessories.moth_wings_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_WINGS_BLOCK), length(SSaccessories.moth_wings_list))]
dna.features["original_moth_wings"] = genetic_value
dna.features["moth_wings"] = genetic_value
if(dna.features["moth_antennae"])
var/genetic_value = SSaccessories.moth_antennae_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_ANTENNAE_BLOCK), length(SSaccessories.moth_antennae_list))]
dna.features["original_moth_antennae"] = genetic_value
dna.features["moth_antennae"] = genetic_value
if(dna.features["moth_markings"])
dna.features["moth_markings"] = SSaccessories.moth_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_MARKINGS_BLOCK), length(SSaccessories.moth_markings_list))]
if(dna.features["caps"])
dna.features["caps"] = SSaccessories.caps_list[deconstruct_block(get_uni_feature_block(features, DNA_MUSHROOM_CAPS_BLOCK), length(SSaccessories.caps_list))]
if(dna.features["pod_hair"])
dna.features["pod_hair"] = SSaccessories.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), length(SSaccessories.pod_hair_list))]
for(var/obj/item/organ/external/external_organ in organs)
external_organ.mutate_feature(features, src)
if(icon_update)
update_body(is_creating = mutcolor_update)
if(mutations_overlay_update)
update_mutations_overlay()
/mob/proc/domutcheck()
return
/mob/living/carbon/domutcheck()
if(!has_dna())
return
for(var/mutation in dna.mutation_index)
dna.check_block(mutation)
update_mutations_overlay()
/datum/dna/proc/check_block(mutation)
var/datum/mutation/human/HM = get_mutation(mutation)
if(check_block_string(mutation))
if(!HM)
. = add_mutation(mutation, MUT_NORMAL)
return
return force_lose(HM)
//Return the active mutation of a type if there is one
/datum/dna/proc/get_mutation(A)
for(var/datum/mutation/human/HM in mutations)
if(istype(HM, A))
return HM
/datum/dna/proc/check_block_string(mutation)
if((LAZYLEN(mutation_index) > DNA_MUTATION_BLOCKS) || !(mutation in mutation_index))
return FALSE
return is_gene_active(mutation)
/datum/dna/proc/is_gene_active(mutation)
return (mutation_index[mutation] == GET_SEQUENCE(mutation))
/datum/dna/proc/set_se(on=TRUE, datum/mutation/human/HM)
if(!HM || !(HM.type in mutation_index) || (LAZYLEN(mutation_index) < DNA_MUTATION_BLOCKS))
return
. = TRUE
if(on)
mutation_index[HM.type] = GET_SEQUENCE(HM.type)
default_mutation_genes[HM.type] = mutation_index[HM.type]
else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type])
mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty)
default_mutation_genes[HM.type] = mutation_index[HM.type]
/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob
if(!mutation)
return FALSE
var/mutation_type = mutation
if(istype(mutation, /datum/mutation/human))
var/datum/mutation/human/M = mutation
mutation_type = M.type
if(!mutation_in_sequence(mutation_type)) //cant activate what we dont have, use add_mutation
return FALSE
add_mutation(mutation, MUT_NORMAL)
return TRUE
/////////////////////////// DNA HELPER-PROCS //////////////////////////////
/datum/dna/proc/mutation_in_sequence(mutation)
if(!mutation)
return
if(istype(mutation, /datum/mutation/human))
var/datum/mutation/human/HM = mutation
if(HM.type in mutation_index)
return TRUE
else if(mutation in mutation_index)
return TRUE
/mob/living/carbon/proc/random_mutate(list/candidates, difficulty = 2)
if(!has_dna())
CRASH("[src] does not have DNA")
var/mutation = pick(candidates)
. = dna.add_mutation(mutation)
/mob/living/carbon/proc/easy_random_mutate(quality = POSITIVE + NEGATIVE + MINOR_NEGATIVE, scrambled = TRUE, sequence = TRUE, exclude_monkey = TRUE, resilient = NONE)
if(!has_dna())
CRASH("[src] does not have DNA")
var/list/mutations = list()
if(quality & POSITIVE)
mutations += GLOB.good_mutations
if(quality & NEGATIVE)
mutations += GLOB.bad_mutations
if(quality & MINOR_NEGATIVE)
mutations += GLOB.not_good_mutations
var/list/possible = list()
for(var/datum/mutation/human/A in mutations)
if((!sequence || dna.mutation_in_sequence(A.type)) && !dna.get_mutation(A.type))
possible += A.type
if(exclude_monkey)
possible.Remove(/datum/mutation/human/race)
if(LAZYLEN(possible))
var/mutation = pick(possible)
. = dna.activate_mutation(mutation)
if(scrambled)
var/datum/mutation/human/HM = dna.get_mutation(mutation)
if(HM)
HM.scrambled = TRUE
if(HM.quality & resilient)
HM.mutadone_proof = TRUE
return TRUE
/mob/living/carbon/proc/random_mutate_unique_identity()
if(!has_dna())
CRASH("[src] does not have DNA")
var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS)
dna.set_uni_feature_block(num, random_string(GET_UI_BLOCK_LEN(num), GLOB.hex_characters))
updateappearance(mutations_overlay_update=1)
/mob/living/carbon/proc/random_mutate_unique_features()
if(!has_dna())
CRASH("[src] does not have DNA")
var/num = rand(1, DNA_FEATURE_BLOCKS)
dna.set_uni_feature_block(num, random_string(GET_UF_BLOCK_LEN(num), GLOB.hex_characters))
updateappearance(mutcolor_update = TRUE, mutations_overlay_update = TRUE)
/mob/living/carbon/proc/clean_dna()
if(!has_dna())
CRASH("[src] does not have DNA")
dna.remove_all_mutations()
/mob/living/carbon/proc/clean_random_mutate(list/candidates, difficulty = 2)
clean_dna()
random_mutate(candidates, difficulty)
/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, uf=FALSE, probability)
if(!M.has_dna())
CRASH("[M] does not have DNA")
if(HAS_TRAIT(M, TRAIT_NO_DNA_SCRAMBLE))
return
if(se)
for(var/i=1, i <= DNA_MUTATION_BLOCKS, i++)
if(prob(probability))
M.dna.generate_dna_blocks()
M.domutcheck()
if(ui)
for(var/blocknum in 1 to DNA_UNI_IDENTITY_BLOCKS)
if(prob(probability))
M.dna.set_uni_feature_block(blocknum, random_string(GET_UI_BLOCK_LEN(blocknum), GLOB.hex_characters))
if(uf)
for(var/blocknum in 1 to DNA_FEATURE_BLOCKS)
if(prob(probability))
M.dna.set_uni_feature_block(blocknum, random_string(GET_UF_BLOCK_LEN(blocknum), GLOB.hex_characters))
if(ui || uf)
M.updateappearance(mutcolor_update=uf, mutations_overlay_update=1)
//value in range 1 to values. values must be greater than 0
//all arguments assumed to be positive integers
/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE)
var/width = round((16**blocksize)/values)
if(value < 1)
value = 1
value = (value * width) - rand(1,width)
return num2hex(value, blocksize)
//value is hex
/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE)
var/width = round((16**blocksize)/values)
value = round(hex2num(value) / width) + 1
if(value > values)
value = values
return value
/proc/get_uni_identity_block(identity, blocknum)
return copytext(identity, GLOB.total_ui_len_by_block[blocknum], LAZYACCESS(GLOB.total_ui_len_by_block, blocknum+1))
/proc/get_uni_feature_block(features, blocknum)
return copytext(features, GLOB.total_uf_len_by_block[blocknum], LAZYACCESS(GLOB.total_uf_len_by_block, blocknum+1))
/////////////////////////// DNA HELPER-PROCS
/mob/living/carbon/human/proc/something_horrible(ignore_stability)
if(!has_dna()) //shouldn't ever happen anyway so it's just in really weird cases
return
if(!ignore_stability && (dna.stability > 0))
return
var/instability = -dna.stability
dna.remove_all_mutations()
dna.stability = 100
if(prob(max(70-instability,0)))
switch(rand(0,11)) //not complete and utter death
if(0)
monkeyize()
if(1)
gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic)
new/obj/vehicle/ridden/wheelchair(get_turf(src)) //don't buckle, because I can't imagine to plethora of things to go through that could otherwise break
to_chat(src, span_warning("My flesh turned into a wheelchair and I can't feel my legs."))
if(2)
corgize()
if(3)
to_chat(src, span_notice("Oh, I actually feel quite alright!"))
if(4)
to_chat(src, span_notice("Oh, I actually feel quite alright!"))
physiology.damage_resistance -= 20000 //you thought
if(5)
to_chat(src, span_notice("Oh, I actually feel quite alright!"))
reagents.add_reagent(/datum/reagent/aslimetoxin, 10)
if(6)
apply_status_effect(/datum/status_effect/go_away)
if(7)
to_chat(src, span_notice("Oh, I actually feel quite alright!"))
ForceContractDisease(new /datum/disease/decloning) // slow acting, non-viral GBS
if(8)
var/list/elligible_organs = list()
for(var/obj/item/organ/internal/internal_organ in organs) //make sure we dont get an implant or cavity item
elligible_organs += internal_organ
vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 10)
if(elligible_organs.len)
var/obj/item/organ/O = pick(elligible_organs)
O.Remove(src)
visible_message(span_danger("[src] vomits up [p_their()] [O.name]!"), span_danger("You vomit up your [O.name]")) //no "vomit up your heart"
O.forceMove(drop_location())
if(prob(20))
O.animate_atom_living()
if(9 to 10)
ForceContractDisease(new/datum/disease/gastrolosis())
to_chat(src, span_notice("Oh, I actually feel quite alright!"))
if(11)
to_chat(src, span_notice("Your DNA mutates into the ultimate biological form!"))
crabize()
else
switch(rand(0,6))
if(0)
investigate_log("has been gibbed by DNA instability.", INVESTIGATE_DEATHS)
gib(DROP_ALL_REMAINS)
if(1)
investigate_log("has been dusted by DNA instability.", INVESTIGATE_DEATHS)
dust()
if(2)
investigate_log("has been transformed into a statue by DNA instability.", INVESTIGATE_DEATHS)
death()
petrify(statue_timer = INFINITY, save_brain = FALSE)
ghostize(FALSE)
if(3)
if(prob(95))
var/obj/item/bodypart/BP = get_bodypart(pick(BODY_ZONE_CHEST,BODY_ZONE_HEAD))
if(BP)
BP.dismember()
else
investigate_log("has been gibbed by DNA instability.", INVESTIGATE_DEATHS)
gib(DROP_ALL_REMAINS)
else
set_species(/datum/species/dullahan)
if(4)
visible_message(span_warning("[src]'s skin melts off!"), span_boldwarning("Your skin melts off!"))
spawn_gibs()
set_species(/datum/species/skeleton)
if(prob(90))
addtimer(CALLBACK(src, PROC_REF(death)), 3 SECONDS)
if(5)
to_chat(src, span_phobia("LOOK UP!"))
addtimer(CALLBACK(src, PROC_REF(something_horrible_mindmelt)), 3 SECONDS)
if(6)
slow_psykerize()
/mob/living/carbon/human/proc/something_horrible_mindmelt()
if(!is_blind())
var/obj/item/organ/internal/eyes/eyes = locate(/obj/item/organ/internal/eyes) in organs
if(!eyes)
return
eyes.Remove(src)
qdel(eyes)
visible_message(span_notice("[src] looks up and their eyes melt away!"), span_userdanger("I understand now."))
addtimer(CALLBACK(src, PROC_REF(adjustOrganLoss), ORGAN_SLOT_BRAIN, 200), 2 SECONDS)