diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index 54194176d6..c4290a9456 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -593,6 +593,8 @@ #define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" ///Whenever EquipRanked is called, called after job is set #define COMSIG_JOB_RECEIVED "job_received" +///When the mob's dna and species have been fully applied +#define COMSIG_HUMAN_DNA_FINALIZED "human_dna_finished" // /datum/species signals diff --git a/code/__defines/dna.dm b/code/__defines/dna.dm index c7ee3eceec..1c0862f48c 100644 --- a/code/__defines/dna.dm +++ b/code/__defines/dna.dm @@ -103,8 +103,8 @@ var/SMALLSIZEBLOCK = 0 #define DNA_UI_GENDER 14 #define DNA_UI_BEARD_STYLE 15 #define DNA_UI_HAIR_STYLE 16 -#define DNA_UI_EAR_STYLE 17 // VOREStation snippet. -#define DNA_UI_EAR_SECONDARY_STYLE 18 // VOREStation snippet. +#define DNA_UI_EAR_STYLE 17 +#define DNA_UI_EAR_SECONDARY_STYLE 18 #define DNA_UI_TAIL_STYLE 19 #define DNA_UI_PLAYERSCALE 20 #define DNA_UI_TAIL_R 21 @@ -152,9 +152,14 @@ var/SMALLSIZEBLOCK = 0 #define DNA_UI_WING2_B 57 #define DNA_UI_WING3_R 58 #define DNA_UI_WING3_G 59 -#define DNA_UI_WING3_B 60 // VOREStation snippet end. +#define DNA_UI_WING3_B 60 #define DNA_UI_WING_ALPHA 61 -#define DNA_UI_LENGTH 61 // VOREStation Edit - Needs to match the highest number above. +#define DNA_UI_GRAD_STYLE 62 +#define DNA_UI_GRAD_R 63 +#define DNA_UI_GRAD_G 64 +#define DNA_UI_GRAD_B 65 +// Needs to match the highest number above. +#define DNA_UI_LENGTH 65 #define DNA_SE_LENGTH 90 // Traitgenes (Expanded from 49 to 84, there have been a considerable expansion of genes. // This leaves room for future expansion. This can be arbitrarily raised without worry if genes start to get crowded. diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index 23e7349dbf..e8222a5735 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -96,17 +96,18 @@ #define ROBOT_NOTIFICATION_AI_SHELL 5 // Appearance change flags -#define APPEARANCE_UPDATE_DNA 0x1 -#define APPEARANCE_RACE (0x2|APPEARANCE_UPDATE_DNA) -#define APPEARANCE_GENDER (0x4|APPEARANCE_UPDATE_DNA) -#define APPEARANCE_SKIN 0x8 -#define APPEARANCE_HAIR 0x10 -#define APPEARANCE_HAIR_COLOR 0x20 -#define APPEARANCE_FACIAL_HAIR 0x40 -#define APPEARANCE_FACIAL_HAIR_COLOR 0x80 -#define APPEARANCE_EYE_COLOR 0x100 +#define APPEARANCE_RACE 0x1 +#define APPEARANCE_GENDER 0x2 +#define APPEARANCE_SKIN 0x4 +#define APPEARANCE_HAIR 0x8 +#define APPEARANCE_HAIR_COLOR 0x10 +#define APPEARANCE_FACIAL_HAIR 0x20 +#define APPEARANCE_FACIAL_HAIR_COLOR 0x40 +#define APPEARANCE_EYE_COLOR 0x80 #define APPEARANCE_ALL_HAIR (APPEARANCE_HAIR|APPEARANCE_HAIR_COLOR|APPEARANCE_FACIAL_HAIR|APPEARANCE_FACIAL_HAIR_COLOR) -#define APPEARANCE_ALL 0xFFFF +#define APPEARANCE_MISC 0x100 +#define APPEARANCE_ALL_COSMETIC (APPEARANCE_GENDER|APPEARANCE_SKIN|APPEARANCE_ALL_HAIR|APPEARANCE_EYE_COLOR|APPEARANCE_MISC) +#define APPEARANCE_ALL 0xFFFF // Click cooldown #define DEFAULT_ATTACK_COOLDOWN 8 //Default timeout for aggressive actions diff --git a/code/__defines/var_copy.dm b/code/__defines/var_copy.dm new file mode 100644 index 0000000000..ca75b966c3 --- /dev/null +++ b/code/__defines/var_copy.dm @@ -0,0 +1,2 @@ +/// Use this when copying vars[] to skip some built in byond ones that get really unhappy when you access them like that. Used like if(BLACKLISTED_COPY_VARS) in switch or list(BLACKLISTED_COPY_VARS) +#define BLACKLISTED_COPY_VARS "ATOM_TOPIC_EXAMINE","type","loc","locs","vars","parent","parent_type","verbs","ckey","key","_active_timers", "_datum_components", "_listen_lookup", "_signal_procs" diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm index 6cae6f4ba3..6306f11f1e 100644 --- a/code/_helpers/global_lists.dm +++ b/code/_helpers/global_lists.dm @@ -308,7 +308,9 @@ GLOBAL_LIST_EMPTY(mannequins) */ // Custom species icon bases - var/list/blacklisted_icons = list(SPECIES_CUSTOM,SPECIES_PROMETHEAN) //VOREStation Edit + ///These are icons that you DO NOT want to be selectable! + var/list/blacklisted_icons = list(SPECIES_CUSTOM,SPECIES_PROMETHEAN) + ///These are icons that you WANT to be selectable, even if they're a whitelist species! var/list/whitelisted_icons = list(SPECIES_FENNEC,SPECIES_XENOHYBRID,SPECIES_VOX,SPECIES_SHADEKIN) //CHOMPedit for(var/species_name in GLOB.playable_species) if(species_name in blacklisted_icons) diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index b0d400619d..0d127ed172 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -861,22 +861,7 @@ Turf and target are seperate in case you want to teleport some distance from a t else O=new original.type(locate(0,0,0)) - var/static/list/blacklisted_var_names = list( - "ATOM_TOPIC_EXAMINE", - "type", - "loc", - "locs", - "vars", - "parent", - "parent_type", - "verbs", - "ckey", - "key", - "_active_timers", // ChompEDIT - blacklist timers - "_datum_components", // ChompEDIT - blacklist DCS - "_listen_lookup", // ChompEDIT - blacklist signal listeners - "_signal_procs" // ChompEDIT - blacklist signal procs - ) + var/static/list/blacklisted_var_names = list(BLACKLISTED_COPY_VARS) if(perfectcopy) if((O) && (original)) for(var/V in original.vars) diff --git a/code/datums/components/species/xenochimera.dm b/code/datums/components/species/xenochimera.dm index 8351ace06e..c830264ed6 100644 --- a/code/datums/components/species/xenochimera.dm +++ b/code/datums/components/species/xenochimera.dm @@ -1,31 +1,43 @@ /datum/component/xenochimera - var/laststress = 0 - var/mob/living/carbon/human/owner + VAR_PRIVATE/laststress = 0 + VAR_PRIVATE/mob/living/carbon/human/owner var/feral = 0 var/revive_ready = REVIVING_READY var/revive_finished = FALSE - var/regen_sounds = list( + VAR_PRIVATE/regen_sounds = list( 'sound/effects/mob_effects/xenochimera/regen_1.ogg', 'sound/effects/mob_effects/xenochimera/regen_2.ogg', 'sound/effects/mob_effects/xenochimera/regen_4.ogg', 'sound/effects/mob_effects/xenochimera/regen_3.ogg', 'sound/effects/mob_effects/xenochimera/regen_5.ogg' ) + VAR_PRIVATE/datum/transhuman/body_record/revival_record /datum/component/xenochimera/Initialize() if(!ishuman(parent)) return COMPONENT_INCOMPATIBLE owner = parent RegisterSignal(owner, COMSIG_XENOCHIMERA_COMPONENT, PROC_REF(handle_comp)) + RegisterSignal(owner, COMSIG_HUMAN_DNA_FINALIZED, PROC_REF(handle_record)) add_verb(owner, /mob/living/carbon/human/proc/reconstitute_form) /datum/component/xenochimera/Destroy(force) UnregisterSignal(owner, COMSIG_XENOCHIMERA_COMPONENT) + UnregisterSignal(owner, COMSIG_HUMAN_DNA_FINALIZED) remove_verb(owner, /mob/living/carbon/human/proc/reconstitute_form) + qdel_null(revival_record) owner = null . = ..() +/datum/component/xenochimera/proc/handle_record() + SIGNAL_HANDLER + if(QDELETED(owner)) + return + qdel_null(revival_record) + revival_record = new(owner) + /datum/component/xenochimera/proc/handle_comp() + SIGNAL_HANDLER if(QDELETED(owner)) return handle_feralness() @@ -42,6 +54,19 @@ if(!owner.lying) owner.lay_down() +/datum/component/xenochimera/proc/set_revival_delay(var/time) + revive_ready = REVIVING_NOW + revive_finished = (world.time + time SECONDS) // When do we finish reviving? Allows us to find out when we're done, called by the alert currently. + +/datum/component/xenochimera/proc/trigger_revival(var/from_save_slot) + ASSERT(revival_record) + if(owner.isSynthetic()) + revival_record.revive_xenochimera(owner,TRUE,from_save_slot) + else + revival_record.revive_xenochimera(owner,FALSE,from_save_slot) + if(from_save_slot) + handle_record() // Update record + /datum/component/xenochimera/proc/handle_feralness() //first, calculate how stressed the chimera is @@ -254,103 +279,102 @@ /mob/living/carbon/human/proc/reconstitute_form() //Scree's race ability.in exchange for: No cloning. set name = "Reconstitute Form" set category = "Abilities.Xenochimera" - + var/datum/component/xenochimera/xc = get_xenochimera_component() + if(!xc) + return if(is_incorporeal()) to_chat(src, "You cannot regenerate while incorporeal.") return // Sanity is mostly handled in chimera_regenerate() if(stat == DEAD) - var/confirm = tgui_alert(src, "Are you sure you want to regenerate your corpse? This process can take up to thirty minutes.", "Confirm Regeneration", list("Yes", "No")) + var/confirm = tgui_alert(src, "Are you sure you want to regenerate your corpse? This process can take up to thirty minutes. Additionally, you may regenerate your appearance based on your current form or the appearance of the currently loaded slot.", "Confirm Regeneration", list("Yes", "No")) if(confirm == "Yes") - chimera_regenerate() - else if (quickcheckuninjured()) - var/confirm = tgui_alert(src, "Are you sure you want to regenerate? As you are uninjured this will only take 30 seconds and match your appearance to your character slot.", "Confirm Regeneration", list("Yes", "No")) + xc.chimera_regenerate() + else if(quickcheckuninjured()) + var/confirm = tgui_alert(src, "Are you sure you want to regenerate? As you are uninjured this will only take 30 seconds. Additionally, you may regenerate your appearance based on your current form or the appearance of the currently loaded slot.", "Confirm Regeneration", list("Yes", "No")) if(confirm == "Yes") - chimera_regenerate() + xc.chimera_regenerate() else - var/confirm = tgui_alert(src, "Are you sure you want to completely reconstruct your form? This process can take up to fifteen minutes, depending on how hungry you are, and you will be unable to move.", "Confirm Regeneration", list("Yes", "No")) + var/confirm = tgui_alert(src, "Are you sure you want to completely reconstruct your form? This process can take up to fifteen minutes, depending on how hungry you are, and you will be unable to move. Additionally, you may regenerate your appearance based on your current form or the appearance of the currently loaded slot", "Confirm Regeneration", list("Yes", "No")) if(confirm == "Yes") - chimera_regenerate() + xc.chimera_regenerate() -/mob/living/carbon/human/proc/chimera_regenerate() - var/datum/component/xenochimera/xc = get_xenochimera_component() - if(!xc) +/datum/component/xenochimera/proc/chimera_regenerate() + if(!owner) return //If they're already regenerating - switch(xc.revive_ready) + switch(revive_ready) if(REVIVING_NOW) - to_chat(src, "You are already reconstructing, just wait for the reconstruction to finish!") + to_chat(owner, "You are already reconstructing, just wait for the reconstruction to finish!") return if(REVIVING_DONE) - to_chat(src, "Your reconstruction is done, but you need to hatch now.") + to_chat(owner, "Your reconstruction is done, but you need to hatch now.") return - if(xc.revive_ready > world.time) - to_chat(src, "You can't use that ability again so soon!") + if(revive_ready > world.time) + to_chat(owner, "You can't use that ability again so soon!") return - var/time = min(900, (120+780/(1 + nutrition/100))) //capped at 15 mins, roughly 6 minutes at 250 (yellow) nutrition, 4.1 minutes at 500 (grey), cannot be below 2 mins - if (quickcheckuninjured()) //if you're completely uninjured, then you get a speedymode - check health first for quickness + var/time = min(900, (120+780/(1 + owner.nutrition/100))) //capped at 15 mins, roughly 6 minutes at 250 (yellow) nutrition, 4.1 minutes at 500 (grey), cannot be below 2 mins + if (owner.quickcheckuninjured()) //if you're completely uninjured, then you get a speedymode - check health first for quickness time = 30 //Clicked regen while dead. - if(stat == DEAD) + if(owner.stat == DEAD) //reviving from dead takes extra nutriment to be provided from outside OR takes twice as long and consumes extra at the end - if(!hasnutriment()) + if(!owner.hasnutriment()) time = time*2 - to_chat(src, "You begin to reconstruct your form. You will not be able to move during this time. It should take aproximately [round(time)] seconds.") + to_chat(owner, "You begin to reconstruct your form. You will not be able to move during this time. It should take aproximately [round(time)] seconds.") //Scary spawnerization. - xc.revive_ready = REVIVING_NOW - xc.revive_finished = (world.time + time SECONDS) // When do we finish reviving? Allows us to find out when we're done, called by the alert currently. - throw_alert("regen", /obj/screen/alert/xenochimera/reconstitution) + set_revival_delay(time) + owner.throw_alert("regen", /obj/screen/alert/xenochimera/reconstitution) addtimer(CALLBACK(src, PROC_REF(chimera_regenerate_ready)), time SECONDS, TIMER_DELETE_ME) //Clicked regen while NOT dead else - to_chat(src, "You begin to reconstruct your form. You will not be able to move during this time. It should take aproximately [round(time)] seconds.") + to_chat(owner, "You begin to reconstruct your form. You will not be able to move during this time. It should take aproximately [round(time)] seconds.") //Waiting for regen after being alive - xc.revive_ready = REVIVING_NOW - xc.revive_finished = (world.time + time SECONDS) // When do we finish reviving? Allows us to find out when we're done, called by the alert currently. - throw_alert("regen", /obj/screen/alert/xenochimera/reconstitution) + set_revival_delay(time) + owner.throw_alert("regen", /obj/screen/alert/xenochimera/reconstitution) addtimer(CALLBACK(src, PROC_REF(chimera_regenerate_nutrition)), time SECONDS, TIMER_DELETE_ME) + owner.lying = TRUE + // open_appearance_editor() -/mob/living/carbon/human/proc/chimera_regenerate_nutrition() - var/datum/component/xenochimera/xc = get_xenochimera_component() - if(!xc) +/datum/component/xenochimera/proc/chimera_regenerate_nutrition() + if(!owner) return //Slightly different flavour messages - if(stat != DEAD || hasnutriment()) - to_chat(src, span_notice("Consciousness begins to stir as your new body awakens, ready to hatch..")) + if(owner.stat != DEAD || owner.hasnutriment()) + to_chat(owner, span_notice("Consciousness begins to stir as your new body awakens, ready to hatch..")) else - to_chat(src, span_warning("Consciousness begins to stir as your battered body struggles to recover from its ordeal..")) - add_verb(src, /mob/living/carbon/human/proc/hatch) - xc.revive_ready = REVIVING_DONE - src << sound('sound/effects/mob_effects/xenochimera/hatch_notification.ogg',0,0,0,30) - clear_alert("regen") - throw_alert("hatch", /obj/screen/alert/xenochimera/readytohatch) + to_chat(owner, span_warning("Consciousness begins to stir as your battered body struggles to recover from its ordeal..")) + add_verb(owner, /mob/living/carbon/human/proc/hatch) + revive_ready = REVIVING_DONE + owner << sound('sound/effects/mob_effects/xenochimera/hatch_notification.ogg',0,0,0,30) + owner.clear_alert("regen") + owner.throw_alert("hatch", /obj/screen/alert/xenochimera/readytohatch) -/mob/living/carbon/human/proc/chimera_regenerate_ready() - var/datum/component/xenochimera/xc = get_xenochimera_component() - if(!xc) +/datum/component/xenochimera/proc/chimera_regenerate_ready() + if(!owner) return // check to see if they've been fixed by outside forces in the meantime such as defibbing - if(stat != DEAD) - to_chat(src, span_notice("Your body has recovered from its ordeal, ready to regenerate itself again.")) - xc.revive_ready = REVIVING_READY //reset their cooldown - clear_alert("regen") - throw_alert("hatch", /obj/screen/alert/xenochimera/readytohatch) + if(owner.stat != DEAD) + to_chat(owner, span_notice("Your body has recovered from its ordeal, ready to regenerate itself again.")) + revive_ready = REVIVING_READY //reset their cooldown + owner.clear_alert("regen") + owner.throw_alert("hatch", /obj/screen/alert/xenochimera/readytohatch) // Was dead, still dead. else - to_chat(src, span_notice("Consciousness begins to stir as your new body awakens, ready to hatch.")) - add_verb(src, /mob/living/carbon/human/proc/hatch) - xc.revive_ready = REVIVING_DONE - src << sound('sound/effects/mob_effects/xenochimera/hatch_notification.ogg',0,0,0,30) - clear_alert("regen") - throw_alert("hatch", /obj/screen/alert/xenochimera/readytohatch) + to_chat(owner, span_notice("Consciousness begins to stir as your new body awakens, ready to hatch.")) + add_verb(owner, /mob/living/carbon/human/proc/hatch) + revive_ready = REVIVING_DONE + owner << sound('sound/effects/mob_effects/xenochimera/hatch_notification.ogg',0,0,0,30) + owner.clear_alert("regen") + owner.throw_alert("hatch", /obj/screen/alert/xenochimera/readytohatch) /mob/living/carbon/human/proc/hatch() set name = "Hatch" @@ -359,71 +383,95 @@ if(!xc) return if(xc.revive_ready != REVIVING_DONE) - //Hwhat? - remove_verb(src, /mob/living/carbon/human/proc/hatch) - return + return //Hwhat? - var/confirm = tgui_alert(src, "Are you sure you want to hatch right now? This will be very obvious to anyone in view.", "Confirm Regeneration", list("Yes", "No")) + // Default is use internal record, even if closes menu + var/reload_slot = tgui_alert(src, "Regenerate from your current form, or from the appearance of your current character slot(This will not change your current species or traits.)", "Regenerate Form", list("Current Form", "From Slot")) + + // Check if valid to load from this slot + var/from_slot = "" + from_slot = "You'll hatch using your current appearance" + if(reload_slot == "From Slot" && client) + if(client.prefs.species == SPECIES_PROTEAN) // Exploit protection + to_chat(src,span_warning("You cannot copy nanoform prosthetic limbs from this species. Please try another character.")) + return + var/slot_is_synth = ((O_BRAIN in client.prefs.organ_data) && client.prefs.organ_data[O_BRAIN]) + if(slot_is_synth && !isSynthetic()) // Prevents some pretty weird situations + to_chat(src,span_warning("Cannot apply character appearance. [slot_is_synth ? "The slot's character is synthetic." : "The slot's character is organic."] Slot must match the current body's synthetic state. Please try another character.")) + return + from_slot = "You'll hatch using [client.prefs.real_name]'s appearance" + + var/confirm = tgui_alert(src, "Are you sure you want to hatch right now? This will be very obvious to anyone in view. [from_slot]! Are you sure?", "Confirm Regeneration", list("Yes", "No")) if(confirm == "Yes") + ///This makes xenochimera shoot out their robotic limbs if they're not a FBP. + if(!isSynthetic()) //If we aren't repairing robotic limbs (FBP) we reject any robot limbs we have and kick them out! + for(var/O in organs_by_name) + var/obj/item/organ/external/organ = organs_by_name[O] + if(!istype(organ, /obj/item/organ/external)) + continue + if(!organ.robotic) + continue + else + organ.removed() + ///End of xenochimera limb rejection code. + //Dead when hatching + //var/sickness_duration = 10 MINUTES //CHOMPedit + var/has_braindamage = FALSE if(stat == DEAD) - // var/sickness_duration = 10 MINUTES //CHOMPedit //Reviving from ded takes extra nutrition - if it isn't provided from outside sources, it comes from you if(!hasnutriment()) nutrition=nutrition * 0.75 - // sickness_duration = 20 MINUTES //CHOMPedit - chimera_hatch() - // add_modifier(/datum/modifier/resleeving_sickness/chimera, sickness_duration) //CHOMPedit + //sickness_duration = 20 MINUTES //CHOMPedit + has_braindamage = TRUE + + // Finalize! + remove_verb(src, /mob/living/carbon/human/proc/hatch) + clear_alert("hatch") + xc.chimera_hatch((reload_slot == "From Slot" && client)) + visible_message(span_warning(span_huge("[src] rises to \his feet."))) //Bloody hell... + if(has_braindamage) + //add_modifier(/datum/modifier/resleeving_sickness/chimera, sickness_duration) //CHOMPedit adjustBrainLoss(5) // if they're reviving from dead, they come back with 5 brainloss on top of whatever's unhealed. - visible_message(span_warning("

" + span_huge("The former corpse staggers to its feet, all its former wounds having vanished...") + "

")) //Bloody hell... - clear_alert("hatch") - return - //Alive when hatching - else - chimera_hatch() - - visible_message(span_warning("

" + span_huge("[src] rises to \his feet.") + "

")) //Bloody hell... - clear_alert("hatch") - -/mob/living/carbon/human/proc/chimera_hatch() - var/datum/component/xenochimera/xc = get_xenochimera_component() - if(!xc) +/datum/component/xenochimera/proc/chimera_hatch(var/from_save_slot) + if(!owner) return - remove_verb(src, /mob/living/carbon/human/proc/hatch) - to_chat(src, span_notice("Your new body awakens, bursting free from your old skin.")) + + remove_verb(owner, /mob/living/carbon/human/proc/hatch) + to_chat(owner, span_notice("Your new body awakens, bursting free from your old skin.")) //Modify and record values (half nutrition and braindamage) - var/old_nutrition = nutrition - var/braindamage = min(5, max(0, (brainloss-1) * 0.5)) //brainloss is tricky to heal and might take a couple of goes to get rid of completely. - var/uninjured=quickcheckuninjured() - //I did have special snowflake code, but this is easier. //It's also EXTREMELY BAD AND LETS THEM SAVEFILE HACK. - revive() - mutations.Remove(HUSK) - setBrainLoss(braindamage) - species.update_vore_belly_def_variant() + var/old_nutrition = owner.nutrition + var/braindamage = min(5, max(0, (owner.brainloss-1) * 0.5)) //brainloss is tricky to heal and might take a couple of goes to get rid of completely. + var/uninjured=owner.quickcheckuninjured() + trigger_revival(from_save_slot) + + owner.mutations.Remove(HUSK) + owner.setBrainLoss(braindamage) + owner.species.update_vore_belly_def_variant() if(!uninjured) - nutrition = old_nutrition * 0.5 + owner.nutrition = old_nutrition * 0.5 //Drop everything - for(var/obj/item/W in src) - drop_from_inventory(W) + for(var/obj/item/W in owner) + owner.drop_from_inventory(W) //Visual effects - var/T = get_turf(src) - var/blood_color = species.blood_color - var/flesh_color = species.flesh_color + var/T = get_turf(owner) + var/blood_color = owner.species.blood_color + var/flesh_color = owner.species.flesh_color new /obj/effect/gibspawner/human/xenochimera(T, null, flesh_color, blood_color) - visible_message(span_danger("

" + span_huge("The lifeless husk of [src] bursts open, revealing a new, intact copy in the pool of viscera.") + "

")) //Bloody hell... + owner.visible_message(span_danger(span_huge("The lifeless husk of [owner] bursts open, revealing a new, intact copy in the pool of viscera."))) //Bloody hell... playsound(T, 'sound/effects/mob_effects/xenochimera/hatch.ogg', 50) else //lower cost for doing a quick cosmetic revive - nutrition = old_nutrition * 0.9 + owner.nutrition = old_nutrition * 0.9 //Unfreeze some things - does_not_breathe = FALSE - update_canmove() - stunned = 2 + owner.does_not_breathe = FALSE + owner.update_canmove() + owner.AdjustStunned(2) - xc.revive_ready = world.time + 10 MINUTES //set the cooldown, Reduced this to 10 minutes, you're playing with fire if you're reviving that often. + revive_ready = world.time + 10 MINUTES //set the cooldown, Reduced this to 10 minutes, you're playing with fire if you're reviving that often. /datum/modifier/resleeving_sickness/chimera //near identical to the regular version, just with different flavortexts name = "imperfect regeneration" diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm index 4a80c02f0d..1f2c4059ad 100644 --- a/code/game/dna/dna2.dm +++ b/code/game/dna/dna2.dm @@ -63,10 +63,6 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) var/gender_specific_species_sounds = FALSE var/species_sounds_male = "None" var/species_sounds_female = "None" - var/grad_style = 0 - var/r_grad = 0 - var/g_grad = 0 - var/b_grad = 0 var/custom_say var/custom_ask var/custom_whisper @@ -74,55 +70,43 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) var/list/custom_heat = list() var/list/custom_cold = list() var/digitigrade = 0 //0, Not FALSE, for future use as indicator for digitigrade types + var/custom_footstep = FOOTSTEP_MOB_SHOE // New stuff var/species = SPECIES_HUMAN var/list/body_markings = list() - var/list/body_markings_genetic = list() var/list/genetic_modifiers = list() // Modifiers with the MODIFIER_GENETIC flag are saved. Note that only the type is saved, not an instance. // Make a copy of this strand. // USE THIS WHEN COPYING STUFF OR YOU'LL GET CORRUPTION! +// Can you imagine, this used to be done manually, var by var? /datum/dna/proc/Clone() var/datum/dna/new_dna = new() - new_dna.unique_enzymes=unique_enzymes - new_dna.b_type=b_type - new_dna.real_name=real_name - new_dna.species=species - new_dna.body_markings=body_markings.Copy() - new_dna.base_species=base_species - new_dna.custom_species=custom_species - new_dna.species_traits=species_traits.Copy() - new_dna.blood_color=blood_color - new_dna.blood_reagents=blood_reagents - new_dna.scale_appearance = scale_appearance - new_dna.offset_override = offset_override - new_dna.synth_markings = synth_markings - new_dna.custom_speech_bubble = custom_speech_bubble - new_dna.species_sounds = species_sounds - new_dna.gender_specific_species_sounds = gender_specific_species_sounds - new_dna.species_sounds_male = species_sounds_male - new_dna.species_sounds_female = species_sounds_female - new_dna.grad_style = grad_style - new_dna.r_grad = r_grad - new_dna.g_grad = g_grad - new_dna.b_grad = b_grad - new_dna.custom_say=custom_say - new_dna.custom_ask=custom_ask - new_dna.custom_whisper=custom_whisper - new_dna.custom_exclaim=custom_exclaim - new_dna.custom_heat=custom_heat - new_dna.custom_cold=custom_cold - new_dna.digitigrade=src.digitigrade - var/list/body_markings_genetic = (body_markings - body_marking_nopersist_list) - new_dna.body_markings=body_markings_genetic.Copy() - for(var/b=1;b<=DNA_SE_LENGTH;b++) - new_dna.SE[b]=SE[b] - if(b<=DNA_UI_LENGTH) - new_dna.UI[b]=UI[b] + for(var/A in vars) + switch(A) + if(BLACKLISTED_COPY_VARS) + continue + if("dirtyUI") + dirtyUI=1 + continue + if("dirtySE") + dirtySE=1 + continue + if("body_markings") + var/list/body_markings_genetic = body_markings.Copy() + body_markings_genetic -= body_marking_nopersist_list + new_dna.vars[A] = body_markings_genetic + continue + if(islist(vars[A])) + var/list/L = vars[A] + new_dna.vars[A] = L.Copy() + continue + new_dna.vars[A] = vars[A] + // Finish up by updating enzymes/identity from our UI/SEs new_dna.UpdateUI() new_dna.UpdateSE() return new_dna + /////////////////////////////////////// // UNIQUE IDENTITY /////////////////////////////////////// @@ -138,6 +122,11 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) if(!defer) UpdateUI() +/** + * Beginning of mob to dna, and dna to mob transfer procs. + * Ensure that ResetUIFrom() and ApplyToMob() mirror each other. All vars should be read FROM the mob, and written back to it! + * ALL dna logic for reading from the mob, storing the dna data, and writing that dna data back to the mob should be here, and ONLY here. + */ /datum/dna/proc/ResetUIFrom(var/mob/living/carbon/human/character) // INITIALIZE! ResetUI(1) @@ -174,6 +163,11 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) if(character.wing_style) wing_style = wing_styles_list.Find(character.wing_style.type) + // Hairgrad + var/grad_style = 0 + if(character.grad_style) + grad_style = GLOB.hair_gradients.Find(character.grad_style) + // Playerscale (This assumes list is sorted big->small) var/size_multiplier = GLOB.player_sizes_list.len // If fail to find, take smallest for(var/N in GLOB.player_sizes_list) @@ -194,10 +188,6 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) src.gender_specific_species_sounds = character.species.gender_specific_species_sounds src.species_sounds_male = character.species.species_sounds_male src.species_sounds_female = character.species.species_sounds_female - src.grad_style = character.grad_style - src.r_grad = character.r_grad - src.g_grad = character.g_grad - src.b_grad = character.b_grad src.species_traits = character.species.traits.Copy() src.custom_say = character.custom_say src.custom_ask = character.custom_ask @@ -206,6 +196,7 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) src.custom_heat = character.custom_heat src.custom_cold = character.custom_cold src.digitigrade = character.digitigrade + src.custom_footstep = character.custom_footstep // +1 to account for the none-of-the-above possibility SetUIValueRange(DNA_UI_EAR_STYLE, ear_style + 1, ear_styles_list.len + 1, 1) @@ -213,6 +204,7 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) SetUIValueRange(DNA_UI_TAIL_STYLE, tail_style + 1, tail_styles_list.len + 1, 1) SetUIValueRange(DNA_UI_PLAYERSCALE, size_multiplier, GLOB.player_sizes_list.len, 1) SetUIValueRange(DNA_UI_WING_STYLE, wing_style + 1, wing_styles_list.len + 1, 1) + SetUIValueRange(DNA_UI_GRAD_STYLE, grad_style, GLOB.hair_gradients.len, 1) SetUIValueRange(DNA_UI_TAIL_R, character.r_tail, 255, 1) SetUIValueRange(DNA_UI_TAIL_G, character.g_tail, 255, 1) @@ -253,6 +245,10 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) SetUIValueRange(DNA_UI_EARS3_B, character.b_ears3, 255, 1) SetUIValueRange(DNA_UI_EARS_ALPHA,character.a_ears, 255, 1) + SetUIValueRange(DNA_UI_GRAD_R, character.r_grad, 255, 1) + SetUIValueRange(DNA_UI_GRAD_G, character.g_grad, 255, 1) + SetUIValueRange(DNA_UI_GRAD_B, character.b_grad, 255, 1) + for(var/channel in 1 to DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) var/offset = DNA_UI_EARS_SECONDARY_START + (channel - 1) * 3 var/list/read_rgb = ReadRGB(LAZYACCESS(character.ear_secondary_colors, channel) || "#ffffff") @@ -294,6 +290,180 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) UpdateUI() +/datum/dna/proc/ApplyToMob(var/mob/living/carbon/human/H) + //////////////////////////////////////////////////////////////////////////////// + // Apply UIs to character + //Hair color + H.r_hair = GetUIValueRange(DNA_UI_HAIR_R, 255) + H.g_hair = GetUIValueRange(DNA_UI_HAIR_G, 255) + H.b_hair = GetUIValueRange(DNA_UI_HAIR_B, 255) + + //Facial hair color + H.r_facial = GetUIValueRange(DNA_UI_BEARD_R, 255) + H.g_facial = GetUIValueRange(DNA_UI_BEARD_G, 255) + H.b_facial = GetUIValueRange(DNA_UI_BEARD_B, 255) + + //Skin color (Tone for humans is seperate) + H.r_skin = GetUIValueRange(DNA_UI_SKIN_R, 255) + H.g_skin = GetUIValueRange(DNA_UI_SKIN_G, 255) + H.b_skin = GetUIValueRange(DNA_UI_SKIN_B, 255) + + H.s_tone = 35 - GetUIValueRange(DNA_UI_SKIN_TONE, 220) // Value can be negative. + + //Eye color + H.r_eyes = GetUIValueRange(DNA_UI_EYES_R, 255) + H.g_eyes = GetUIValueRange(DNA_UI_EYES_G, 255) + H.b_eyes = GetUIValueRange(DNA_UI_EYES_B, 255) + H.update_eyes() + + //Hair gradient color + H.r_grad = GetUIValueRange(DNA_UI_GRAD_R, 255) + H.g_grad = GetUIValueRange(DNA_UI_GRAD_G, 255) + H.b_grad = GetUIValueRange(DNA_UI_GRAD_B, 255) + + //Sex... Needs future support for properly handling things other then just male/female. UIs have the capability to do so! + if(H.gender != NEUTER) + if (GetUIState(DNA_UI_GENDER)) + H.gender = FEMALE + else + H.gender = MALE + + //Body markings + for(var/tag in body_markings) + var/obj/item/organ/external/E = H.organs_by_name[tag] + if(E) + var/list/marklist = body_markings[tag] + E.markings = marklist.Copy() + + //Hair style + var/hair = GetUIValueRange(DNA_UI_HAIR_STYLE,hair_styles_list.len) + if((0 < hair) && (hair <= hair_styles_list.len)) + H.h_style = hair_styles_list[hair] + + //Facial Hair + var/beard = GetUIValueRange(DNA_UI_BEARD_STYLE,facial_hair_styles_list.len) + if((0 < beard) && (beard <= facial_hair_styles_list.len)) + H.f_style = facial_hair_styles_list[beard] + + // Ears + var/ears = GetUIValueRange(DNA_UI_EAR_STYLE, ear_styles_list.len + 1) - 1 + if(ears < 1) + H.ear_style = null + else if((0 < ears) && (ears <= ear_styles_list.len)) + H.ear_style = ear_styles_list[ear_styles_list[ears]] + var/ears_secondary = GetUIValueRange(DNA_UI_EAR_SECONDARY_STYLE, ear_styles_list.len + 1) - 1 + if(ears_secondary < 1) + H.ear_secondary_style = null + else if((0 < ears_secondary) && (ears_secondary <= ear_styles_list.len)) + H.ear_secondary_style = ear_styles_list[ear_styles_list[ears_secondary]] + + // Ear Color + H.r_ears = GetUIValueRange(DNA_UI_EARS_R, 255) + H.g_ears = GetUIValueRange(DNA_UI_EARS_G, 255) + H.b_ears = GetUIValueRange(DNA_UI_EARS_B, 255) + H.r_ears2 = GetUIValueRange(DNA_UI_EARS2_R, 255) + H.g_ears2 = GetUIValueRange(DNA_UI_EARS2_G, 255) + H.b_ears2 = GetUIValueRange(DNA_UI_EARS2_B, 255) + H.r_ears3 = GetUIValueRange(DNA_UI_EARS3_R, 255) + H.g_ears3 = GetUIValueRange(DNA_UI_EARS3_G, 255) + H.b_ears3 = GetUIValueRange(DNA_UI_EARS3_B, 255) + H.a_ears = GetUIValueRange(DNA_UI_EARS_ALPHA, 255) + H.a_ears2 = GetUIValueRange(DNA_UI_EARS_SECONDARY_ALPHA, 255) + + LAZYINITLIST(H.ear_secondary_colors) + H.ear_secondary_colors.len = max(length(H.ear_secondary_colors), DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) + for(var/channel in 1 to DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) + var/offset = DNA_UI_EARS_SECONDARY_START + (channel - 1) * 3 + H.ear_secondary_colors[channel] = rgb( + GetUIValueRange(offset, 255), + GetUIValueRange(offset + 1, 255), + GetUIValueRange(offset + 2, 255), + ) + + //Tail + var/tail = GetUIValueRange(DNA_UI_TAIL_STYLE, tail_styles_list.len + 1) - 1 + if(tail < 1) + H.tail_style = null + else if((0 < tail) && (tail <= tail_styles_list.len)) + H.tail_style = tail_styles_list[tail_styles_list[tail]] + + //Wing + var/wing = GetUIValueRange(DNA_UI_WING_STYLE, wing_styles_list.len + 1) - 1 + if(wing < 1) + H.wing_style = null + else if((0 < wing) && (wing <= wing_styles_list.len)) + H.wing_style = wing_styles_list[wing_styles_list[wing]] + + //Wing Color + H.r_wing = GetUIValueRange(DNA_UI_WING_R, 255) + H.g_wing = GetUIValueRange(DNA_UI_WING_G, 255) + H.b_wing = GetUIValueRange(DNA_UI_WING_B, 255) + H.r_wing2 = GetUIValueRange(DNA_UI_WING2_R, 255) + H.g_wing2 = GetUIValueRange(DNA_UI_WING2_G, 255) + H.b_wing2 = GetUIValueRange(DNA_UI_WING2_B, 255) + H.r_wing3 = GetUIValueRange(DNA_UI_WING3_R, 255) + H.g_wing3 = GetUIValueRange(DNA_UI_WING3_G, 255) + H.b_wing3 = GetUIValueRange(DNA_UI_WING3_B, 255) + H.a_wing = GetUIValueRange(DNA_UI_WING_ALPHA, 255) + + // Playerscale + var/size = GetUIValueRange(DNA_UI_PLAYERSCALE, GLOB.player_sizes_list.len) + if((0 < size) && (size <= GLOB.player_sizes_list.len)) + H.resize(GLOB.player_sizes_list[GLOB.player_sizes_list[size]], TRUE, ignore_prefs = TRUE) + + // Tail/Taur Color + H.r_tail = GetUIValueRange(DNA_UI_TAIL_R, 255) + H.g_tail = GetUIValueRange(DNA_UI_TAIL_G, 255) + H.b_tail = GetUIValueRange(DNA_UI_TAIL_B, 255) + H.r_tail2 = GetUIValueRange(DNA_UI_TAIL2_R, 255) + H.g_tail2 = GetUIValueRange(DNA_UI_TAIL2_G, 255) + H.b_tail2 = GetUIValueRange(DNA_UI_TAIL2_B, 255) + H.r_tail3 = GetUIValueRange(DNA_UI_TAIL3_R, 255) + H.g_tail3 = GetUIValueRange(DNA_UI_TAIL3_G, 255) + H.b_tail3 = GetUIValueRange(DNA_UI_TAIL3_B, 255) + H.a_tail = GetUIValueRange(DNA_UI_TAIL_ALPHA, 255) + + // Hair gradiant + var/grad = GetUIValueRange(DNA_UI_GRAD_STYLE,GLOB.hair_gradients.len) + if((0 < grad) && (grad <= GLOB.hair_gradients.len)) + H.grad_style = GLOB.hair_gradients[grad] + + //////////////////////////////////////////////////////////////////////////////// + // Custom species and other cosmetic vars + H.custom_species = custom_species + H.custom_say = custom_say + H.custom_ask = custom_ask + H.custom_whisper = custom_whisper + H.custom_exclaim = custom_exclaim + H.custom_speech_bubble = custom_speech_bubble + H.custom_heat = custom_heat + H.custom_cold = custom_cold + H.custom_footstep = custom_footstep + H.digitigrade = digitigrade + + // If synths have character markings + H.synth_markings = synth_markings + + // Scaling style + H.fuzzy = scale_appearance + H.offset_override = offset_override + + //////////////////////////////////////////////////////////////////////////////// + // Get a copy of the species datum to edit for ourselves + // anything that sets stuff in species MUST be done beyond here! + H.species.produceCopy(species_traits, H, base_species, FALSE) // Traitgenes edit - reset_dna flag required, or genes get reset on resleeve + + // Update species blood with our blood color from dna! + H.species.blood_reagents = blood_reagents + H.species.blood_color = blood_color + H.species.species_sounds = species_sounds + H.species.gender_specific_species_sounds = gender_specific_species_sounds + H.species.species_sounds_male = species_sounds_male + H.species.species_sounds_female = species_sounds_female +/** + * End of mob to dna, and dna to mob transfer procs. + */ + // Set a DNA UI block's raw value. /datum/dna/proc/SetUIValue(var/block,var/value,var/defer=0) if (block<=0) return diff --git a/code/game/dna/dna2_helpers.dm b/code/game/dna/dna2_helpers.dm index 96d4162c85..950808873d 100644 --- a/code/game/dna/dna2_helpers.dm +++ b/code/game/dna/dna2_helpers.dm @@ -132,172 +132,29 @@ return output // Use mob.UpdateAppearance() +/mob/proc/UpdateAppearance(var/list/UI=null) + return FALSE // Simpler. Don't specify UI in order for the mob to use its own. -/mob/proc/UpdateAppearance(var/list/UI=null) - if(ishuman(src)) - if(UI!=null) - src.dna.UI=UI - src.dna.UpdateUI() - dna.check_integrity() - var/mob/living/carbon/human/H = src - H.r_hair = dna.GetUIValueRange(DNA_UI_HAIR_R, 255) - H.g_hair = dna.GetUIValueRange(DNA_UI_HAIR_G, 255) - H.b_hair = dna.GetUIValueRange(DNA_UI_HAIR_B, 255) +/mob/living/carbon/human/UpdateAppearance(var/list/UI=null) + // Rebuild off UI arg if not null + if(UI!=null) + src.dna.UI=UI + src.dna.UpdateUI() - H.r_facial = dna.GetUIValueRange(DNA_UI_BEARD_R, 255) - H.g_facial = dna.GetUIValueRange(DNA_UI_BEARD_G, 255) - H.b_facial = dna.GetUIValueRange(DNA_UI_BEARD_B, 255) + // Setup dna + dna.check_integrity() + dna.ApplyToMob(src) - H.r_skin = dna.GetUIValueRange(DNA_UI_SKIN_R, 255) - H.g_skin = dna.GetUIValueRange(DNA_UI_SKIN_G, 255) - H.b_skin = dna.GetUIValueRange(DNA_UI_SKIN_B, 255) + // Apply dna changes to organ icons + force_update_organs() + force_update_limbs() - H.r_eyes = dna.GetUIValueRange(DNA_UI_EYES_R, 255) - H.g_eyes = dna.GetUIValueRange(DNA_UI_EYES_G, 255) - H.b_eyes = dna.GetUIValueRange(DNA_UI_EYES_B, 255) - H.update_eyes() + //H.update_body(0) //Done in force_update_limbs already + update_eyes() + update_hair() - H.s_tone = 35 - dna.GetUIValueRange(DNA_UI_SKIN_TONE, 220) // Value can be negative. - - if(H.gender != NEUTER) - if (dna.GetUIState(DNA_UI_GENDER)) - H.gender = FEMALE - else - H.gender = MALE - - //Body markings - for(var/tag in dna.body_markings) - var/obj/item/organ/external/E = H.organs_by_name[tag] - if(E) - var/list/marklist = dna.body_markings[tag] - E.markings = marklist.Copy() - - //Hair - var/hair = dna.GetUIValueRange(DNA_UI_HAIR_STYLE,hair_styles_list.len) - if((0 < hair) && (hair <= hair_styles_list.len)) - H.h_style = hair_styles_list[hair] - - //Facial Hair - var/beard = dna.GetUIValueRange(DNA_UI_BEARD_STYLE,facial_hair_styles_list.len) - if((0 < beard) && (beard <= facial_hair_styles_list.len)) - H.f_style = facial_hair_styles_list[beard] - - // Ears - var/ears = dna.GetUIValueRange(DNA_UI_EAR_STYLE, ear_styles_list.len + 1) - 1 - if(ears < 1) - H.ear_style = null - else if((0 < ears) && (ears <= ear_styles_list.len)) - H.ear_style = ear_styles_list[ear_styles_list[ears]] - var/ears_secondary = dna.GetUIValueRange(DNA_UI_EAR_SECONDARY_STYLE, ear_styles_list.len + 1) - 1 - if(ears_secondary < 1) - H.ear_secondary_style = null - else if((0 < ears_secondary) && (ears_secondary <= ear_styles_list.len)) - H.ear_secondary_style = ear_styles_list[ear_styles_list[ears_secondary]] - - // Ear Color - H.r_ears = dna.GetUIValueRange(DNA_UI_EARS_R, 255) - H.g_ears = dna.GetUIValueRange(DNA_UI_EARS_G, 255) - H.b_ears = dna.GetUIValueRange(DNA_UI_EARS_B, 255) - H.r_ears2 = dna.GetUIValueRange(DNA_UI_EARS2_R, 255) - H.g_ears2 = dna.GetUIValueRange(DNA_UI_EARS2_G, 255) - H.b_ears2 = dna.GetUIValueRange(DNA_UI_EARS2_B, 255) - H.r_ears3 = dna.GetUIValueRange(DNA_UI_EARS3_R, 255) - H.g_ears3 = dna.GetUIValueRange(DNA_UI_EARS3_G, 255) - H.b_ears3 = dna.GetUIValueRange(DNA_UI_EARS3_B, 255) - H.a_ears = dna.GetUIValueRange(DNA_UI_EARS_ALPHA, 255) - H.a_ears2 = dna.GetUIValueRange(DNA_UI_EARS_SECONDARY_ALPHA, 255) - - LAZYINITLIST(H.ear_secondary_colors) - H.ear_secondary_colors.len = max(length(H.ear_secondary_colors), DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) - for(var/channel in 1 to DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) - var/offset = DNA_UI_EARS_SECONDARY_START + (channel - 1) * 3 - H.ear_secondary_colors[channel] = rgb( - dna.GetUIValueRange(offset, 255), - dna.GetUIValueRange(offset + 1, 255), - dna.GetUIValueRange(offset + 2, 255), - ) - - //Tail - var/tail = dna.GetUIValueRange(DNA_UI_TAIL_STYLE, tail_styles_list.len + 1) - 1 - if(tail < 1) - H.tail_style = null - else if((0 < tail) && (tail <= tail_styles_list.len)) - H.tail_style = tail_styles_list[tail_styles_list[tail]] - - //Wing - var/wing = dna.GetUIValueRange(DNA_UI_WING_STYLE, wing_styles_list.len + 1) - 1 - if(wing < 1) - H.wing_style = null - else if((0 < wing) && (wing <= wing_styles_list.len)) - H.wing_style = wing_styles_list[wing_styles_list[wing]] - - //Wing Color - H.r_wing = dna.GetUIValueRange(DNA_UI_WING_R, 255) - H.g_wing = dna.GetUIValueRange(DNA_UI_WING_G, 255) - H.b_wing = dna.GetUIValueRange(DNA_UI_WING_B, 255) - H.r_wing2 = dna.GetUIValueRange(DNA_UI_WING2_R, 255) - H.g_wing2 = dna.GetUIValueRange(DNA_UI_WING2_G, 255) - H.b_wing2 = dna.GetUIValueRange(DNA_UI_WING2_B, 255) - H.r_wing3 = dna.GetUIValueRange(DNA_UI_WING3_R, 255) - H.g_wing3 = dna.GetUIValueRange(DNA_UI_WING3_G, 255) - H.b_wing3 = dna.GetUIValueRange(DNA_UI_WING3_B, 255) - H.a_wing = dna.GetUIValueRange(DNA_UI_WING_ALPHA, 255) - - // Playerscale - var/size = dna.GetUIValueRange(DNA_UI_PLAYERSCALE, GLOB.player_sizes_list.len) - if((0 < size) && (size <= GLOB.player_sizes_list.len)) - H.resize(GLOB.player_sizes_list[GLOB.player_sizes_list[size]], TRUE, ignore_prefs = TRUE) - - // Tail/Taur Color - H.r_tail = dna.GetUIValueRange(DNA_UI_TAIL_R, 255) - H.g_tail = dna.GetUIValueRange(DNA_UI_TAIL_G, 255) - H.b_tail = dna.GetUIValueRange(DNA_UI_TAIL_B, 255) - H.r_tail2 = dna.GetUIValueRange(DNA_UI_TAIL2_R, 255) - H.g_tail2 = dna.GetUIValueRange(DNA_UI_TAIL2_G, 255) - H.b_tail2 = dna.GetUIValueRange(DNA_UI_TAIL2_B, 255) - H.r_tail3 = dna.GetUIValueRange(DNA_UI_TAIL3_R, 255) - H.g_tail3 = dna.GetUIValueRange(DNA_UI_TAIL3_G, 255) - H.b_tail3 = dna.GetUIValueRange(DNA_UI_TAIL3_B, 255) - H.a_tail = dna.GetUIValueRange(DNA_UI_TAIL_ALPHA, 255) - - // Technically custom_species is not part of the UI, but this place avoids merge problems. - H.custom_species = dna.custom_species - H.custom_say = dna.custom_say - H.custom_ask = dna.custom_ask - H.custom_whisper = dna.custom_whisper - H.custom_exclaim = dna.custom_exclaim - H.species.blood_color = dna.blood_color - H.fuzzy = dna.scale_appearance - H.offset_override = dna.offset_override - H.synth_markings = dna.synth_markings - H.custom_speech_bubble = dna.custom_speech_bubble - H.grad_style = dna.grad_style - H.r_grad = dna.r_grad - H.g_grad = dna.g_grad - H.b_grad = dna.b_grad - H.custom_heat = dna.custom_heat - H.custom_cold = dna.custom_cold - var/datum/species/S = H.species - S.produceCopy(dna.species_traits, H, dna.base_species, FALSE) // Traitgenes edit - reset_dna flag required, or genes get reset on resleeve - H.dna.blood_reagents = dna.blood_reagents - H.dna.blood_color = dna.blood_color - H.species.blood_reagents = H.dna.blood_reagents - H.species.blood_color = H.dna.blood_color - H.species.species_sounds = dna.species_sounds - H.species.gender_specific_species_sounds = dna.gender_specific_species_sounds - H.species.species_sounds_male = dna.species_sounds_male - H.species.species_sounds_female = dna.species_sounds_female - - H.force_update_organs() //VOREStation Add - Gotta do this too - H.force_update_limbs() - //H.update_body(0) //VOREStation Edit - Done in force_update_limbs already - H.update_eyes() - H.update_hair() - - return 1 - else - return 0 + return TRUE /mob/living/carbon/human/proc/force_update_organs() for(var/obj/item/organ/O as anything in organs + internal_organs) diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm index 4ff878e536..fd88c04978 100644 --- a/code/game/dna/dna_modifier.dm +++ b/code/game/dna/dna_modifier.dm @@ -38,16 +38,21 @@ /datum/dna2/record/proc/copy() var/datum/dna2/record/newrecord = new /datum/dna2/record - qdel_swap(newrecord.dna, dna.Clone()) - newrecord.types = types - newrecord.name = name - newrecord.mind = mind - newrecord.ckey = ckey - newrecord.languages = languages - newrecord.implant = implant - newrecord.flavor = flavor - newrecord.gender = gender - newrecord.genetic_modifiers = genetic_modifiers.Copy() + for(var/A in vars) + switch(A) + if(BLACKLISTED_COPY_VARS) + continue + if("id") + newrecord.id = copytext(md5(dna.real_name), 2, 6) // update this specially + continue + if("dna") + qdel_swap(newrecord.dna, dna.Clone()) + continue + if(islist(vars[A])) + var/list/L = vars[A] + newrecord.vars[A] = L.Copy() + continue + newrecord.vars[A] = vars[A] return newrecord @@ -65,7 +70,7 @@ interact_offline = 1 circuit = /obj/item/circuitboard/clonescanner var/locked = 0 - var/datum/weakref/occupant = null + VAR_PRIVATE/datum/weakref/weakref_occupant = null var/obj/item/reagent_containers/glass/beaker = null var/opened = 0 var/damage_coeff @@ -81,6 +86,18 @@ eject_occupant() . = ..() +/obj/machinery/dna_scannernew/proc/set_occupant(var/mob/living/L) + SHOULD_NOT_OVERRIDE(TRUE) + if(!L) + weakref_occupant = null + return + weakref_occupant = WEAKREF(L) + +/obj/machinery/dna_scannernew/proc/get_occupant() + RETURN_TYPE(/mob/living) + SHOULD_NOT_OVERRIDE(TRUE) + return weakref_occupant?.resolve() + /obj/machinery/dna_scannernew/RefreshParts() scan_level = 0 damage_coeff = 0 @@ -112,7 +129,7 @@ return /obj/machinery/dna_scannernew/proc/eject_occupant() - var/mob/living/carbon/WC = occupant?.resolve() + var/mob/living/carbon/WC = get_occupant() go_out() for(var/obj/O in src) if((!istype(O,/obj/item/reagent_containers)) && (!istype(O,/obj/item/circuitboard/clonescanner)) && (!istype(O,/obj/item/stock_parts)) && (!istype(O,/obj/item/stack/cable_coil))) @@ -122,7 +139,7 @@ M.forceMove(get_turf(src)) /obj/machinery/dna_scannernew/MouseDrop_T(var/mob/target, var/mob/user) //Allows borgs to clone people without external assistance - var/mob/living/carbon/WC = occupant?.resolve() + var/mob/living/carbon/WC = get_occupant() if(user.stat || user.lying || !Adjacent(user) || !target.Adjacent(user)|| !ishuman(target) || WC) return // Traitgenes Do not allow buckled or ridden mobs @@ -144,13 +161,13 @@ if(!ishuman(usr) && !issmall(usr)) //Make sure they're a mob that has dna to_chat(usr, span_notice("Try as you might, you can not climb up into the scanner.")) return - if(occupant) + var/mob/living/carbon/WC = get_occupant() + if(WC) to_chat(usr, span_warning("The scanner is already occupied!")) return if(usr.abiotic()) to_chat(usr, span_warning("The subject cannot have abiotic items on.")) return - var/mob/living/carbon/WC = occupant?.resolve() if(WC) to_chat(usr, span_warning("There is already something inside.")) return @@ -158,7 +175,7 @@ usr.client.perspective = EYE_PERSPECTIVE usr.client.eye = src usr.forceMove(src) - occupant = WEAKREF(usr) + set_occupant(usr) icon_state = "scanner_1" add_fingerprint(usr) SStgui.update_uis(src) @@ -182,7 +199,7 @@ return else if(istype(item, /obj/item/organ/internal/brain)) - if(occupant) + if(get_occupant()) to_chat(user, span_warning("The scanner is already occupied!")) return var/obj/item/organ/internal/brain/brain = item @@ -202,7 +219,7 @@ var/obj/item/grab/G = item if(!ismob(G.affecting)) return - if(occupant) + if(get_occupant()) to_chat(user, span_warning("The scanner is already occupied!")) return if(G.affecting.abiotic()) @@ -219,10 +236,10 @@ if(beaker) beaker.forceMove(get_turf(src)) beaker = null - if(occupant) - var/mob/living/carbon/WC = occupant.resolve() + var/mob/living/carbon/WC = get_occupant() + if(WC) WC.forceMove(get_turf(src)) - occupant = null + set_occupant(null) // Disconnect from our terminal for(var/dirfind in GLOB.cardinal) var/obj/machinery/computer/scan_consolenew/console = locate(/obj/machinery/computer/scan_consolenew, get_step(src, dirfind)) @@ -237,7 +254,7 @@ M.client.perspective = EYE_PERSPECTIVE M.client.eye = src M.forceMove(src) - occupant = WEAKREF(M) + set_occupant(M) icon_state = "scanner_1" // search for ghosts, if the corpse is empty and the scanner is connected to a cloner @@ -254,9 +271,9 @@ SStgui.update_uis(src) /obj/machinery/dna_scannernew/proc/go_out() - if((!(occupant) || locked)) + var/mob/living/carbon/WC = get_occupant() + if((!WC || locked)) return - var/mob/living/carbon/WC = occupant.resolve() if(WC.client) WC.client.eye = WC.client.mob WC.client.perspective = MOB_PERSPECTIVE @@ -268,7 +285,7 @@ break else WC.forceMove(loc) - occupant = null + set_occupant(null) icon_state = "scanner_0" SStgui.update_uis(src) @@ -395,7 +412,7 @@ tgui_interact(user) /obj/machinery/computer/scan_consolenew/tgui_interact(mob/user, datum/tgui/ui) - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() if(!connected || user == WC || user.stat) return ui = SStgui.try_update_ui(user, src, ui) @@ -408,7 +425,7 @@ var/data[0] data["selectedMenuKey"] = selected_menu_key data["locked"] = src.connected.locked - data["hasOccupant"] = connected.occupant ? 1 : 0 + data["hasOccupant"] = connected.get_occupant() ? 1 : 0 data["isInjectorReady"] = injector_ready @@ -448,7 +465,7 @@ data["selectedUITargetHex"] = selected_ui_target_hex var/occupantData[0] - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() if(!WC || !WC.dna) occupantData["name"] = null occupantData["stat"] = null @@ -522,7 +539,7 @@ return TRUE if("toggleLock") playsound(src, 'sound/machines/button.ogg', 30, 1, 0) - if(connected && connected.occupant) + if(connected && connected.get_occupant()) connected.locked = !(connected.locked) return TRUE @@ -541,9 +558,9 @@ return TRUE if("injectRejuvenators") playsound(src, 'sound/machines/button.ogg', 30, 1, 0) - if(!connected.occupant || !connected.beaker) + if(!connected.get_occupant() || !connected.beaker) return TRUE - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() var/inject_amount = clamp(round(text2num(params["amount"]), 5), 0, 50) // round to nearest 5 and clamp to 0-50 if(!inject_amount) return TRUE @@ -561,9 +578,9 @@ selected_se_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE) return TRUE if("pulseSERadiation") - if(!connected?.occupant) + if(!connected?.get_occupant()) return TRUE - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() playsound(src, "keyboard", 40) var/block = WC.dna.GetSESubBlock(selected_se_block,selected_se_subblock) //var/original_block=block @@ -604,7 +621,7 @@ // Traitgenes Moved SE and UI saves to storing the entire body record if("saveDNA") playsound(src, "keyboard", 40) // into console - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() if(WC && WC.dna) // Traitgenes Properly clone records var/datum/transhuman/body_record/databuf = new /datum/transhuman/body_record() @@ -631,7 +648,7 @@ tgui_modal_input(src, "changeBufferLabel", "Please enter the new buffer label:", null, list("id" = bufferId), buffer.mydna.name, TGUI_MODAL_INPUT_MAX_LENGTH_NAME) return TRUE if("transfer") - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() if(!WC || (NOCLONE in WC.mutations) || !WC.dna) return TRUE irradiating = 2 @@ -673,7 +690,7 @@ playsound(src, "keyboard", 40) var/datum/transhuman/body_record/buf = buffers[bufferId] // Send printable record to first sleevepod in area - print_sleeve(usr, buf) + print_sleeve(ui.user, buf) return TRUE if("wipeDisk") @@ -774,12 +791,12 @@ to_chat(user, span_danger( "Error: Cannot grow synthetic.")) return //No pods - var/obj/machinery/clonepod/transhuman/pod = locate() in get_area(src) + var/obj/machinery/clonepod/pod = locate() in get_area(src) if(!pod) to_chat(user, span_danger( "Error: No growpods detected.")) return //Already doing someone. - if(pod.occupant) + if(pod.get_occupant()) to_chat(user, span_danger( "Error: Growpod is currently occupied.")) return //Not enough materials. @@ -805,7 +822,7 @@ to_chat(user, span_notice( "Initiating growing cycle...")) /obj/machinery/computer/scan_consolenew/proc/do_irradiate(var/lock_state, var/block) - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() irradiating = 0 connected.locked = lock_state if(!WC) @@ -840,7 +857,7 @@ WC.regenerate_icons() /obj/machinery/computer/scan_consolenew/proc/do_pulse(var/lock_state) - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() irradiating = 0 connected.locked = lock_state @@ -872,7 +889,7 @@ playsound(src, "keyboard", 40) - var/mob/living/carbon/WC = connected?.occupant?.resolve() + var/mob/living/carbon/WC = connected?.get_occupant() if(!WC) return TRUE var/datum/transhuman/body_record/buf = buffers[bufferId] // Traitgenes- Use bodyrecords diff --git a/code/game/gamemodes/changeling/powers/absorb.dm b/code/game/gamemodes/changeling/powers/absorb.dm index 8efc8f293f..f5ca7e53fb 100644 --- a/code/game/gamemodes/changeling/powers/absorb.dm +++ b/code/game/gamemodes/changeling/powers/absorb.dm @@ -77,7 +77,8 @@ to_chat(src, span_notice("We can now re-adapt, reverting our evolution so that we may start anew, if needed.")) - var/datum/absorbed_dna/newDNA = new(T.real_name, T.dna, T.species.name, T.languages, T.identifying_gender, T.flavor_texts, T.modifiers) + var/saved_dna = T.dna.Clone() /// Prevent transforming bugginess. + var/datum/absorbed_dna/newDNA = new(T.real_name, saved_dna, T.species.name, T.languages, T.identifying_gender, T.flavor_texts, T.modifiers) absorbDNA(newDNA) if(T.mind && T.mind.changeling) diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 83bb1e11a5..68fddae556 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -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.") + "
" + span_bold(span_large("Your recent memories are fuzzy, and it's hard to remember anything from today...")) + \ "
" + 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" diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 5bb3dd67a6..f962921d1a 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -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 diff --git a/code/game/machinery/protean_reconstitutor.dm b/code/game/machinery/protean_reconstitutor.dm index 5fef0e960c..26090eb29f 100644 --- a/code/game/machinery/protean_reconstitutor.dm +++ b/code/game/machinery/protean_reconstitutor.dm @@ -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 diff --git a/code/game/machinery/virtual_reality/ar_console.dm b/code/game/machinery/virtual_reality/ar_console.dm index a0893aa9d0..c3ac5e35c2 100644 --- a/code/game/machinery/virtual_reality/ar_console.dm +++ b/code/game/machinery/virtual_reality/ar_console.dm @@ -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 diff --git a/code/game/machinery/virtual_reality/vr_console.dm b/code/game/machinery/virtual_reality/vr_console.dm index a248c2ab32..b190c28f70 100644 --- a/code/game/machinery/virtual_reality/vr_console.dm +++ b/code/game/machinery/virtual_reality/vr_console.dm @@ -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. diff --git a/code/game/machinery/virtual_reality/vr_procs.dm b/code/game/machinery/virtual_reality/vr_procs.dm index 0afd9e562e..40f0a2dd68 100644 --- a/code/game/machinery/virtual_reality/vr_procs.dm +++ b/code/game/machinery/virtual_reality/vr_procs.dm @@ -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) diff --git a/code/game/objects/structures/ghost_pods/event_vr.dm b/code/game/objects/structures/ghost_pods/event_vr.dm index d1d6ae07fe..6ad09905b1 100644 --- a/code/game/objects/structures/ghost_pods/event_vr.dm +++ b/code/game/objects/structures/ghost_pods/event_vr.dm @@ -257,6 +257,8 @@ if(is_lang_whitelisted(src,chosen_language) || (new_character.species && (chosen_language.name in new_character.species.secondary_langs))) new_character.add_language(lang) + SEND_SIGNAL(new_character, COMSIG_HUMAN_DNA_FINALIZED) + new_character.regenerate_icons() new_character.update_transform() diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 9e54293f82..7d57cd453d 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -561,6 +561,8 @@ Traitors and the like can also be revived with the previous role mostly intact. if(is_lang_whitelisted(src,chosen_language) || (new_character.species && (chosen_language.name in new_character.species.secondary_langs))) new_character.add_language(lang) + SEND_SIGNAL(new_character, COMSIG_HUMAN_DNA_FINALIZED) + //If desired, apply equipment. if(equipment) if(charjob) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 147e216d85..f55911f9a3 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -533,7 +533,7 @@ var/list/preferences_datums = list() else var/bodytype var/datum/species/selected_species = GLOB.all_species[species] - if(custom_base) + if(selected_species.selects_bodytype && custom_base) //Everyone technically has custom_base set to HUMAN, but only some species actually select it. bodytype = custom_base else bodytype = selected_species.get_bodytype() @@ -544,7 +544,8 @@ var/list/preferences_datums = list() for(var/N in character.organs_by_name) var/obj/item/organ/external/O = character.organs_by_name[N] - O.markings.Cut() + if(O) + O.markings.Cut() var/priority = 0 for(var/M in body_markings) @@ -554,6 +555,7 @@ var/list/preferences_datums = list() for(var/BP in mark_datum.body_parts) var/obj/item/organ/external/O = character.organs_by_name[BP] if(O) + if(!islist(body_markings[M][BP])) continue O.markings[M] = list("color" = body_markings[M][BP]["color"], "datum" = mark_datum, "priority" = priority, "on" = body_markings[M][BP]["on"]) character.markings_len = priority @@ -581,7 +583,6 @@ var/list/preferences_datums = list() character.fuzzy = fuzzy character.offset_override = offset_override character.voice_freq = voice_freq - character.size_multiplier = size_multiplier character.resize(size_multiplier, animate = FALSE, ignore_prefs = TRUE) var/list/traits_to_copy = list(/datum/trait/neutral/tall, @@ -616,17 +617,16 @@ var/list/preferences_datums = list() var/datum/species/selected_species = GLOB.all_species[species] var/bodytype_selected - if(custom_base) + if(selected_species.selects_bodytype && custom_base) bodytype_selected = custom_base else bodytype_selected = selected_species.get_bodytype(character) - character.dna.base_species = bodytype_selected character.species.base_species = bodytype_selected character.species.icobase = character.species.get_icobase() character.species.deform = character.species.get_icobase(get_deform = TRUE) character.species.vanity_base_fit = bodytype_selected - if (istype(character.species, /datum/species/shapeshifter)) + if(istype(character.species, /datum/species/shapeshifter)) wrapped_species_by_ref["\ref[character]"] = bodytype_selected character.custom_species = custom_species diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index 77e5f3f7ed..061315b9f4 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -801,11 +801,11 @@ //We REALLY don't need to go through every variable. Doing so makes this lag like hell on 515 /datum/species/proc/copy_variables(var/datum/species/S, var/list/whitelist) //List of variables to ignore, trying to copy type will runtime. - //var/list/blacklist = list("type", "loc", "client", "ckey") + //var/list/blacklist = list(BLACKLISTED_COPY_VARS) //Makes thorough copy of species datum. for(var/i in whitelist) - if(!(i in S.vars)) //Don't copy incompatible vars. - continue + //if(!(i in S.vars)) // This check SOUNDS like a good idea, until you realize it loops over every var in base datum + species datum + byond builtin vars for EACH var in the whitelist. All the vars in whitelist are in the base species datum anyway, so this is unneeded. + // continue if(S.vars[i] != vars[i] && !islist(vars[i])) //If vars are same, no point in copying. S.vars[i] = vars[i] diff --git a/code/modules/mob/living/carbon/human/species/station/prometheans.dm b/code/modules/mob/living/carbon/human/species/station/prometheans.dm index 9aa6085f5d..5dad808f13 100644 --- a/code/modules/mob/living/carbon/human/species/station/prometheans.dm +++ b/code/modules/mob/living/carbon/human/species/station/prometheans.dm @@ -109,16 +109,8 @@ var/datum/species/shapeshifter/promethean/prometheans cold_discomfort_strings = list("You feel too cool.") inherent_verbs = list( - /mob/living/carbon/human/proc/shapeshifter_select_shape, - /mob/living/carbon/human/proc/shapeshifter_select_colour, - /mob/living/carbon/human/proc/shapeshifter_select_hair, - /mob/living/carbon/human/proc/shapeshifter_select_hair_colors, - /mob/living/carbon/human/proc/shapeshifter_select_gender, + /mob/living/carbon/human/proc/innate_shapeshifting, /mob/living/carbon/human/proc/regenerate, - /mob/living/carbon/human/proc/shapeshifter_select_wings, - /mob/living/carbon/human/proc/shapeshifter_select_tail, - /mob/living/carbon/human/proc/shapeshifter_select_ears, - /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, /mob/living/carbon/human/proc/prommie_blobform, /mob/living/proc/set_size, /mob/living/carbon/human/proc/promethean_select_opaqueness, @@ -410,3 +402,9 @@ var/datum/species/shapeshifter/promethean/prometheans return else prommie_intoblob() + +/mob/living/carbon/human/proc/innate_shapeshifting() + set name = "Transform Appearance" + set category = "Abilities.Superpower" + var/datum/tgui_module/appearance_changer/innate/I = new(src, src) + I.tgui_interact(src) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index c49300dae3..9aa66cc739 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -679,7 +679,12 @@ return 1 return 0 +/// Revives a body using the client's preferences if human /mob/living/proc/revive() + revival_healing_action() + +/// Performs the actual healing of Aheal, seperate from revive() because it does not use client prefs. Will not heal everything, and expects to be called through revive() or with a bodyrecord doing a respawn/revive. +/mob/living/proc/revival_healing_action() rejuvenate() if(buckled) buckled.unbuckle_mob() @@ -701,6 +706,8 @@ if(ai_holder) // AI gets told to sleep when killed. Since they're not dead anymore, wake it up. ai_holder.go_wake() + SEND_SIGNAL(src, COMSIG_HUMAN_DNA_FINALIZED) + /mob/living/proc/rejuvenate() if(reagents) reagents.clear_reagents() diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 38524fcb44..2d14ec4d01 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -447,15 +447,15 @@ var/datum/language/keylang = GLOB.all_languages[client.prefs.language_custom_keys[key]] if(keylang) new_character.language_keys[key] = keylang - // VOREStation Add: Preferred Language Setting; if(client.prefs.preferred_language) // Do we have a preferred language? var/datum/language/def_lang = GLOB.all_languages[client.prefs.preferred_language] if(def_lang) new_character.default_language = def_lang - // VOREStation Add End // And uncomment this, too. //new_character.dna.UpdateSE() + SEND_SIGNAL(new_character, COMSIG_HUMAN_DNA_FINALIZED) + // Do the initial caching of the player's body icons. new_character.force_update_limbs() new_character.update_icons_body() diff --git a/code/modules/mob/new_player/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories.dm index fbbff18767..f1610ac5b5 100644 --- a/code/modules/mob/new_player/sprite_accessories.dm +++ b/code/modules/mob/new_player/sprite_accessories.dm @@ -37,7 +37,7 @@ GLOBAL_LIST_INIT(fancy_sprite_accessory_color_channel_names, list("Primary", "Se var/gender = NEUTER // Restrict some styles to specific species. Default to all species to avoid runtimes in character creator. - var/list/species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL, SPECIES_HANNER, SPECIES_ZADDAT, SPECIES_SPARKLE) + var/list/species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL, SPECIES_HANNER, SPECIES_ZADDAT, SPECIES_SPARKLE, SPECIES_PROMETHEAN) // Whether or not the accessory can be affected by colouration var/do_colouration = 1 diff --git a/code/modules/mob/new_player/sprite_accessories_ear.dm b/code/modules/mob/new_player/sprite_accessories_ear.dm index e25e6cd28a..ccf20f9046 100644 --- a/code/modules/mob/new_player/sprite_accessories_ear.dm +++ b/code/modules/mob/new_player/sprite_accessories_ear.dm @@ -15,7 +15,7 @@ var/extra_overlay2 var/desc = DEVELOPER_WARNING_NAME em_block = TRUE - species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL, SPECIES_HANNER, SPECIES_SPARKLE) + species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL, SPECIES_HANNER, SPECIES_SPARKLE, SPECIES_PROMETHEAN) /** * Gets the number of color channels we have. diff --git a/code/modules/mob/new_player/sprite_accessories_wing.dm b/code/modules/mob/new_player/sprite_accessories_wing.dm index 5210bc4fb3..b374f77826 100644 --- a/code/modules/mob/new_player/sprite_accessories_wing.dm +++ b/code/modules/mob/new_player/sprite_accessories_wing.dm @@ -21,7 +21,7 @@ var/extra_overlay_w // Flapping state for extra overlay var/extra_overlay2_w - species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL, SPECIES_HANNER, SPECIES_SPARKLE) + species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJARAN, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL, SPECIES_HANNER, SPECIES_SPARKLE, SPECIES_PROMETHEAN) var/wing_offset = 0 var/multi_dir = FALSE // Does it use different sprites at different layers? _front will be added for sprites on low layer, _back to high layer diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm index 4b33061d11..6cc54fbf57 100644 --- a/code/modules/mob/say_vr.dm +++ b/code/modules/mob/say_vr.dm @@ -514,3 +514,5 @@ var/new_speech_bubble = tgui_input_list(src, "Pick new voice (default for automatic selection)", "Character Preference", GLOB.selectable_speech_bubbles) if(new_speech_bubble) custom_speech_bubble = new_speech_bubble + if(dna) + dna.custom_speech_bubble = new_speech_bubble diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm index f97a3f0927..421bdeb036 100644 --- a/code/modules/organs/internal/brain.dm +++ b/code/modules/organs/internal/brain.dm @@ -216,6 +216,9 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) ..() /obj/item/organ/internal/brain/slime/proc/reviveBody() + // TODO - Reference how xenochimera component handles revival from bodyrecord in the future. + // This requires a promie/protean component for transformation and regeneration. + // This shouldn't use a brain mob for caching dna. That's what BRs are for. var/datum/dna2/record/R = new /datum/dna2/record() qdel_swap(R.dna, brainmob.dna.Clone()) R.ckey = brainmob.ckey @@ -293,8 +296,10 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) for(var/datum/language/L in R.languages) H.add_language(L.name) H.flavor_texts = R.flavor.Copy() - qdel(R.dna) - qdel(R) + + SEND_SIGNAL(H, COMSIG_HUMAN_DNA_FINALIZED) + + qdel(R) // Record already deletes dna qdel(src) return 1 diff --git a/code/modules/reagents/reactions/instant/instant_ch.dm b/code/modules/reagents/reactions/instant/instant_ch.dm index 9b07554e7a..2c0f7fca11 100644 --- a/code/modules/reagents/reactions/instant/instant_ch.dm +++ b/code/modules/reagents/reactions/instant/instant_ch.dm @@ -151,9 +151,9 @@ if(comp.revive_ready >= 1) // if it's not reviving, start doing so comp.revive_ready = REVIVING_READY // overrides the normal cooldown H.visible_message(span_info("[H] shudders briefly, then relaxes, faint movements stirring within.")) - H.chimera_regenerate() + comp.chimera_regenerate() else if(comp.revive_ready == REVIVING_DONE)// already reviving, check if they're ready to hatch - H.chimera_hatch() + comp.chimera_hatch() H.visible_message(span_danger(span_huge("[H] violently convulses and then bursts open, revealing a new, intact copy in the pool of viscera."))) // Hope you were wearing waterproofs, doc... H.adjustBrainLoss(10) // they're reviving from dead, so take 10 brainloss else //they're already reviving but haven't hatched. Give a little message to tell them to wait. diff --git a/code/modules/resleeving/autoresleever.dm b/code/modules/resleeving/autoresleever.dm index 92d3aa3eb5..49f904b3d5 100644 --- a/code/modules/resleeving/autoresleever.dm +++ b/code/modules/resleeving/autoresleever.dm @@ -188,6 +188,8 @@ if(def_lang) new_character.default_language = def_lang + SEND_SIGNAL(new_character, COMSIG_HUMAN_DNA_FINALIZED) + //If desired, apply equipment. if(equip_body) if(charjob) diff --git a/code/modules/resleeving/computers.dm b/code/modules/resleeving/computers.dm index 0ec0973fdb..5626c87fd6 100644 --- a/code/modules/resleeving/computers.dm +++ b/code/modules/resleeving/computers.dm @@ -148,16 +148,17 @@ var/list/clonepods = list() for(var/obj/machinery/clonepod/transhuman/pod in pods) var/status = "idle" + var/mob/living/occupant = pod.get_occupant() if(pod.mess) status = "mess" - else if(pod.occupant && !(pod.stat & NOPOWER)) + else if(occupant && !(pod.stat & NOPOWER)) status = "cloning" clonepods += 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"] = clonepods @@ -177,8 +178,8 @@ resleevers += list(list( "sleever" = REF(resleever), "name" = sanitize(capitalize(resleever.name)), - "occupied" = !!resleever.occupant, - "occupant" = resleever.occupant ? resleever.occupant.real_name : "None" + "occupied" = !!resleever.get_occupant(), + "occupant" = resleever.get_occupant() ? resleever.get_occupant().real_name : "None" )) data["sleevers"] = resleevers @@ -322,7 +323,7 @@ return //Already doing someone. - if(pod.occupant) + if(pod.get_occupant()) set_temp("Error: Growpod is currently occupied.", "danger") active_br = null return @@ -375,26 +376,26 @@ switch(mode) if(1) //Body resleeving //No body to sleeve into. - if(!sleever.occupant) + if(!sleever.get_occupant()) set_temp("Error: Resleeving pod is not occupied.", "danger") active_mr = null return //OOC body lock thing. - if(sleever.occupant.resleeve_lock && active_mr.ckey != sleever.occupant.resleeve_lock) + if(sleever.get_occupant().resleeve_lock && active_mr.ckey != sleever.get_occupant().resleeve_lock) set_temp("Error: Mind incompatible with body.", "danger") active_mr = null return var/list/subtargets = list() - for(var/mob/living/carbon/human/H in sleever.occupant) + for(var/mob/living/carbon/human/H in sleever.get_occupant()) if(H.resleeve_lock && active_mr.ckey != H.resleeve_lock) continue subtargets += H if(subtargets.len) - var/oc_sanity = sleever.occupant + var/oc_sanity = sleever.get_occupant() override = tgui_input_list(ui.user,"Multiple bodies detected. Select target for resleeving of [active_mr.mindname] manually. Sleeving of primary body is unsafe with sub-contents, and is not listed.", "Resleeving Target", subtargets) - if(!override || oc_sanity != sleever.occupant || !(override in sleever.occupant)) + if(!override || oc_sanity != sleever.get_occupant() || !(override in sleever.get_occupant())) set_temp("Error: Target selection aborted.", "danger") active_mr = null return @@ -558,7 +559,7 @@ if(!selected_sleever) can_sleeve_active = FALSE set_temp("Error: Cannot sleeve due to no selected sleever.", "danger") - if(selected_sleever && !selected_sleever.occupant) + if(selected_sleever && !selected_sleever.get_occupant()) can_sleeve_active = FALSE set_temp("Error: Cannot sleeve due to lack of sleever occupant.", "danger") else diff --git a/code/modules/resleeving/designer.dm b/code/modules/resleeving/designer.dm index 4736e68952..96b39af2a3 100644 --- a/code/modules/resleeving/designer.dm +++ b/code/modules/resleeving/designer.dm @@ -59,6 +59,7 @@ if(!designer_gui) designer_gui = new(src, null) designer_gui.linked_body_design_console = WEAKREF(src) + CallAsync(designer_gui, TYPE_PROC_REF(/datum/tgui_module/appearance_changer,jiggle_map)) if(!designer_gui.owner) designer_gui.make_fake_owner() selected_record = FALSE diff --git a/code/modules/resleeving/infocore_records.dm b/code/modules/resleeving/infocore_records.dm index 30b905fd76..44778f995a 100644 --- a/code/modules/resleeving/infocore_records.dm +++ b/code/modules/resleeving/infocore_records.dm @@ -134,6 +134,11 @@ speciesname = M.custom_species ? M.custom_species : null bodygender = M.gender body_oocnotes = M.ooc_notes + body_ooclikes = M.ooc_notes_likes + body_oocdislikes = M.ooc_notes_dislikes + body_oocfavs = M.ooc_notes_favs + body_oocmaybes = M.ooc_notes_maybes + body_oocstyle = M.ooc_notes_style sizemult = M.size_multiplier weight = M.weight aflags = M.appearance_flags @@ -204,7 +209,6 @@ if(add_to_db) SStranscore.add_body(src, database_key = database_key) - /** * Make a deep copy of this record so it can be saved on a disk without modifications * to the original affecting the copy. @@ -214,28 +218,195 @@ /datum/transhuman/body_record/proc/init_from_br(var/datum/transhuman/body_record/orig) ASSERT(!QDELETED(orig)) ASSERT(istype(orig)) - src.mydna = new () - qdel_swap(src.mydna.dna, orig.mydna.dna.Clone()) - src.mydna.ckey = orig.mydna.ckey - src.mydna.id = orig.mydna.id - src.mydna.name = orig.mydna.name - src.mydna.types = orig.mydna.types - src.mydna.flavor = orig.mydna.flavor.Copy() - src.ckey = orig.ckey - src.locked = orig.locked - src.client_ref = orig.client_ref - src.mind_ref = orig.mind_ref - src.synthetic = orig.synthetic - src.speciesname = orig.speciesname - src.bodygender = orig.bodygender - src.body_oocnotes = orig.body_oocnotes - src.body_ooclikes = orig.body_ooclikes - src.body_oocdislikes = orig.body_oocdislikes - src.limb_data = orig.limb_data.Copy() - src.organ_data = orig.organ_data.Copy() - src.genetic_modifiers = orig.genetic_modifiers.Copy() - src.toocomplex = orig.toocomplex - src.sizemult = orig.sizemult - src.aflags = orig.aflags - src.breath_type = orig.breath_type - src.weight = orig.weight + for(var/A in vars) + switch(A) + if(BLACKLISTED_COPY_VARS) + continue + if("mydna") + mydna = orig.mydna.copy() + continue + if(islist(vars[A])) + var/list/L = orig.vars[A] + vars[A] = L.Copy() + continue + vars[A] = orig.vars[A] + +/** + * Spawning a body was once left entirely up to the machine doing it, but bodies are massivley complex + * objects, and doing it this way lead to huge amounts of copypasted code to do the same thing. + * If you want to spawn a body from a BR, please use these... + */ + +/// The core of resleeving, creates a mob based on the current record +/datum/transhuman/body_record/proc/produce_human_mob(var/location, var/is_synthfab, var/force_unlock, var/backup_name) + // These are broken up into steps, otherwise the proc gets massive and hard to read. + var/mob/living/carbon/human/H = internal_producebody(location,backup_name) + internal_producebody_handlesleevelock(H,force_unlock) + internal_producebody_updatelimbandorgans(H) + internal_producebody_updatednastate(H,is_synthfab) + internal_producebody_virgoOOC(H) + internal_producebody_misc(H) + return H + +/// Creates a human mob with the correct species, name, and a stable state. +/datum/transhuman/body_record/proc/internal_producebody(var/location,var/backup_name) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + var/mob/living/carbon/human/H = new /mob/living/carbon/human(location, mydna.dna.species) + if(!mydna.dna.real_name) + mydna.dna.real_name = backup_name + H.real_name = mydna.dna.real_name + H.name = H.real_name + for(var/datum/language/L in mydna.languages) + H.add_language(L.name) + H.suiciding = 0 + H.losebreath = 0 + H.mind = null + + return H + +/// Sets the new body's sleevelock status, to prevent impersonation by transfering an incorrect mind. +/datum/transhuman/body_record/proc/internal_producebody_handlesleevelock(var/mob/living/carbon/human/H,var/force_unlock) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + if(locked && !force_unlock) + if(ckey) + H.resleeve_lock = ckey + else + // Ensure even body scans without an attached ckey respect locking + H.resleeve_lock = "@badckey" + +/// Either converts limbs to robotics or prosthetic states, or removes them entirely based off record. +/datum/transhuman/body_record/proc/internal_producebody_updatelimbandorgans(var/mob/living/carbon/human/H,var/is_synthfab) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + //Fix the external organs + for(var/part in limb_data) + var/status = limb_data[part] + if(status == null) continue //Species doesn't have limb? Child of amputated limb? + + var/obj/item/organ/external/O = H.organs_by_name[part] + if(!O) continue //Not an organ. Perhaps another amputation removed it already. + + if(status == 1) //Normal limbs + continue + else if(status == 0) //Missing limbs + O.remove_rejuv() + else if(status) //Anything else is a manufacturer + if(!is_synthfab) + O.remove_rejuv() //Don't robotize them, leave them removed so robotics can attach a part. + else + O.robotize(status) + + //Then the internal organs + for(var/part in organ_data) + var/status = organ_data[part] + if(status == null) continue //Species doesn't have organ? Child of missing part? + + var/obj/item/organ/I = H.internal_organs_by_name[part] + if(!I) continue//Not an organ. Perhaps external conversion changed it already? + + if(status == 0) //Normal organ + continue + else if(status == 1) //Assisted organ + I.mechassist() + else if(status == 2) //Mechanical organ + I.robotize() + else if(status == 3) //Digital organ + I.digitize() + +/// Transfers dna data to mob, and reinits traits and appearance from it +/datum/transhuman/body_record/proc/internal_producebody_updatednastate(var/mob/living/carbon/human/H,var/is_synthfab) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + //Apply DNA from record + if(!mydna.dna) // This case should never happen, but copied from clone pod... Who knows with this codebase. + mydna.dna = new /datum/dna() + qdel_swap(H.dna, mydna.dna.Clone()) + H.original_player = ckey + + //Apply genetic modifiers, synths don't use these + if(!is_synthfab) + for(var/modifier_type in mydna.genetic_modifiers) + H.add_modifier(modifier_type) + + //Update appearance, remake icons + H.UpdateAppearance() + H.sync_dna_traits(FALSE) // Traitgenes Sync traits to genetics if needed + H.sync_organ_dna() + H.regenerate_icons() + H.initialize_vessel() + +/// Transfers VORE related information cached in the mob +/datum/transhuman/body_record/proc/internal_producebody_virgoOOC(var/mob/living/carbon/human/H) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + H.ooc_notes = body_oocnotes + H.ooc_notes_likes = body_ooclikes + H.ooc_notes_dislikes = body_oocdislikes + H.ooc_notes_favs = body_oocfavs + H.ooc_notes_maybes = body_oocmaybes + H.ooc_notes_style = body_oocstyle + +/datum/transhuman/body_record/proc/internal_producebody_misc(var/mob/living/carbon/human/H) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + H.flavor_texts = mydna.flavor.Copy() + H.resize(sizemult, FALSE) + H.appearance_flags = aflags + H.weight = weight + if(speciesname) + H.custom_species = speciesname + +/** + * Specialty revival procs. Uses the BR for data, but needs to handle some weird logic for xenochi/slimes + */ +/datum/transhuman/body_record/proc/revive_xenochimera(var/mob/living/carbon/human/H,var/heal_robot_limbs,var/from_save_slot) + // Boy this one is complex, but what do we expect when trying to heal damage and organ loss in this game! + if(!H || QDELETED(H)) // Someone, somewhere, will call this without any safety. I feel it in my bones cappin' + return + + // Don't unlock unwilling xenochi! + internal_producebody_handlesleevelock(H,FALSE) + + // Reset our organs/limbs. + H.species.create_organs(H) + internal_producebody_updatelimbandorgans(H, heal_robot_limbs) + + //Don't boot out anyone already in the mob. + if(!H.client || !H.key) + for (var/obj/item/organ/internal/brain/CH in GLOB.all_brain_organs) + if(CH.brainmob) + if(CH.brainmob.real_name == H.real_name) + if(CH.brainmob.mind) + CH.brainmob.mind.transfer_to(H) + qdel(CH) + + // Traitgenes Disable all traits currently active, before species.produceCopy() applies them during updatednastate(). Relevant here as genetraits may not match prior dna! + for(var/datum/gene/trait/gene in GLOB.dna_genes) + if(gene.name in H.active_genes) + gene.deactivate(H) + H.active_genes -= gene.name + + internal_producebody_updatednastate(H,FALSE) + internal_producebody_virgoOOC(H) // Is this needed? + internal_producebody_misc(H) + + // Begin actual REVIVIAL. Do NOT use revive(). That uses client prefs and allows save hacking. + H.revival_healing_action() + + // Update record from vanity copy of slot if needed + if(from_save_slot) + H.client.prefs.vanity_copy_to(H,FALSE,TRUE,TRUE,FALSE) + for(var/category in H.all_underwear) // No undies + H.hide_underwear[category] = TRUE + H.update_underwear() + + return H + +/datum/transhuman/body_record/proc/revive_promethean(var/mob/living/carbon/human/H) + // TODO - See note in code\modules\organs\internal\brain.dm for slime brains + return diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm index d1c9021270..4291f02287 100644 --- a/code/modules/resleeving/machines.dm +++ b/code/modules/resleeving/machines.dm @@ -29,86 +29,9 @@ remove_biomass(CLONE_BIOMASS) //Get the DNA and generate a new mob - var/datum/dna2/record/R = current_project.mydna - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species) - if(current_project.locked) - H.resleeve_lock = current_project.ckey //CHOMPAdd, keep the lock - /*CHOMPRemove Start - if(current_project.ckey) - H.resleeve_lock = current_project.ckey - else - // Ensure even body scans without an attached ckey respect locking - H.resleeve_lock = "@badckey" - *///CHOMPRemove End + var/mob/living/carbon/human/H = current_project.produce_human_mob(src,FALSE,FALSE,"clone ([rand(0,999)])") + SEND_SIGNAL(H, COMSIG_HUMAN_DNA_FINALIZED) - //Fix the external organs - for(var/part in current_project.limb_data) - - var/status = current_project.limb_data[part] - if(status == null) continue //Species doesn't have limb? Child of amputated limb? - - var/obj/item/organ/external/O = H.organs_by_name[part] - if(!O) continue //Not an organ. Perhaps another amputation removed it already. - - if(status == 1) //Normal limbs - continue - else if(status == 0) //Missing limbs - O.remove_rejuv() - else if(status) //Anything else is a manufacturer - O.remove_rejuv() //Don't robotize them, leave them removed so robotics can attach a part. - - //Look, this machine can do this because [reasons] okay?! - for(var/part in current_project.organ_data) - - var/status = current_project.organ_data[part] - if(status == null) continue //Species doesn't have organ? Child of missing part? - - var/obj/item/organ/I = H.internal_organs_by_name[part] - if(!I) continue//Not an organ. Perhaps external conversion changed it already? - - if(status == 0) //Normal organ - continue - else if(status == 1) //Assisted organ - I.mechassist() - else if(status == 2) //Mechanical organ - I.robotize() - else if(status == 3) //Digital organ - I.digitize() - - - occupant = H - - //Set the name or generate one - if(!R.dna.real_name) - R.dna.real_name = "clone ([rand(0,999)])" - H.real_name = R.dna.real_name - - //Apply DNA - qdel_swap(H.dna, R.dna.Clone()) - H.original_player = current_project.ckey - - //Apply genetic modifiers - for(var/modifier_type in R.genetic_modifiers) - H.add_modifier(modifier_type) - - //Apply legs - H.digitigrade = R.dna.digitigrade // ensure clone mob has digitigrade var set appropriately - if(H.dna.digitigrade <> R.dna.digitigrade) - H.dna.digitigrade = R.dna.digitigrade // ensure cloned DNA is set appropriately from record??? for some reason it doesn't get set right despite the override to datum/dna/Clone() - - //Apply damage - H.adjustCloneLoss((H.getMaxHealth() - (H.getMaxHealth()))*-0.75) - H.Paralyse(4) - H.updatehealth() - - //Update appearance, remake icons - H.UpdateAppearance() - H.sync_dna_traits(FALSE) // Traitgenes Sync traits to genetics if needed - H.sync_organ_dna() - H.regenerate_icons() - H.initialize_vessel() - - // Traitgenes Moved breathing equipment to AFTER the genes set it //Give breathing equipment if needed if(current_project.breath_type != null && current_project.breath_type != GAS_O2) H.equip_to_slot_or_del(new /obj/item/clothing/mask/breath(H), slot_wear_mask) @@ -124,25 +47,11 @@ if(istype(H.internal,/obj/item/tank) && H.internals) H.internals.icon_state = "internal1" - //Basically all the VORE stuff - H.ooc_notes = current_project.body_oocnotes - H.ooc_notes_likes = current_project.body_ooclikes - H.ooc_notes_dislikes = current_project.body_oocdislikes - H.ooc_notes_favs = current_project.body_oocfavs - H.ooc_notes_maybes = current_project.body_oocmaybes - H.ooc_notes_style = current_project.body_oocstyle - H.flavor_texts = current_project.mydna.flavor.Copy() - H.resize(current_project.sizemult, FALSE) - H.appearance_flags = current_project.aflags - H.weight = current_project.weight - if(current_project.speciesname) - H.custom_species = current_project.speciesname - - //Suiciding var - H.suiciding = 0 - - //Making double-sure this is not set - H.mind = null + //Apply damage + set_occupant(H) + H.adjustCloneLoss((H.getMaxHealth() - (H.getMaxHealth()))*-0.75) + H.Paralyse(4) + H.updatehealth() //Machine specific stuff at the end update_icon() @@ -150,6 +59,7 @@ return 1 /obj/machinery/clonepod/transhuman/process() + var/mob/living/occupant = get_occupant() if(stat & NOPOWER) if(occupant) locked = 0 @@ -190,7 +100,7 @@ return else if((!occupant) || (occupant.loc != src)) - occupant = null + set_occupant(null) if(locked) locked = 0 update_icon() @@ -199,13 +109,14 @@ return /obj/machinery/clonepod/transhuman/get_completion() + var/mob/living/occupant = get_occupant() if(occupant) return 100 * ((occupant.health + (occupant.getMaxHealth()))) / (occupant.getMaxHealth() + abs(occupant.getMaxHealth())) return 0 /obj/machinery/clonepod/transhuman/examine(mob/user, infix, suffix) . = ..() - if(occupant) + if(get_occupant()) var/completion = get_completion() . += "Progress: [round(completion)]% [chat_progress_bar(round(completion), TRUE)]" @@ -298,103 +209,16 @@ return //Get the DNA and generate a new mob - var/datum/dna2/record/R = current_project.mydna - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species) - if(current_project.locked) - H.resleeve_lock = current_project.ckey //CHOMPAdd, keep the lock - /*CHOMPRemove Start - if(current_project.ckey) - H.resleeve_lock = current_project.ckey - else - // Ensure even body scans without an attached ckey respect locking - H.resleeve_lock = "@badckey" - *///CHOMPRemove End - - //Fix the external organs - for(var/part in current_project.limb_data) - - var/status = current_project.limb_data[part] - if(status == null) continue //Species doesn't have limb? Child of amputated limb? - - var/obj/item/organ/external/O = H.organs_by_name[part] - if(!O) continue //Not an organ. Perhaps another amputation removed it already. - - if(status == 1) //Normal limbs - continue - else if(status == 0) //Missing limbs - O.remove_rejuv() - else if(status) //Anything else is a manufacturer - O.robotize(status) - - //Then the internal organs - for(var/part in current_project.organ_data) - - var/status = current_project.organ_data[part] - if(status == null) continue //Species doesn't have organ? Child of missing part? - - var/obj/item/organ/I = H.internal_organs_by_name[part] - if(!I) continue//Not an organ. Perhaps external conversion changed it already? - - if(status == 0) //Normal organ - continue - else if(status == 1) //Assisted organ - I.mechassist() - else if(status == 2) //Mechanical organ - I.robotize() - else if(status == 3) //Digital organ - I.digitize() - - //Set the name or generate one - if(!R.dna.real_name) - R.dna.real_name = "synth ([rand(0,999)])" - H.real_name = R.dna.real_name - - //Apply DNA - qdel_swap(H.dna, R.dna.Clone()) - H.original_player = current_project.ckey - - //Apply legs - H.digitigrade = R.dna.digitigrade // ensure clone mob has digitigrade var set appropriately - if(H.dna.digitigrade <> R.dna.digitigrade) - H.dna.digitigrade = R.dna.digitigrade // ensure cloned DNA is set appropriately from record??? for some reason it doesn't get set right despite the override to datum/dna/Clone() + var/mob/living/carbon/human/H = current_project.produce_human_mob(src,TRUE,FALSE,"synth ([rand(0,999)])") + SEND_SIGNAL(H, COMSIG_HUMAN_DNA_FINALIZED) //Apply damage H.adjustBruteLoss(brute_value) H.adjustFireLoss(burn_value) H.updatehealth() - //Update appearance, remake icons - H.UpdateAppearance() - H.sync_dna_traits(FALSE) // Traitgenes Sync traits to genetics if needed - H.sync_organ_dna() - H.regenerate_icons() - H.initialize_vessel() - - //Basically all the VORE stuff - H.ooc_notes = current_project.body_oocnotes - H.ooc_notes_likes = current_project.body_ooclikes - H.ooc_notes_dislikes = current_project.body_oocdislikes - //CHOMPEdit Start - H.ooc_notes_favs = current_project.body_oocfavs - H.ooc_notes_maybes = current_project.body_oocmaybes - H.ooc_notes_style = current_project.body_oocstyle - //CHOMPEdit End - H.flavor_texts = current_project.mydna.flavor.Copy() - H.resize(current_project.sizemult) - H.appearance_flags = current_project.aflags - H.weight = current_project.weight - if(current_project.speciesname) - H.custom_species = current_project.speciesname - - //Suiciding var - H.suiciding = 0 - - //Making double-sure this is not set - H.mind = null - //Plonk them here. - H.regenerate_icons() - H.loc = get_turf(src) + H.forceMove(get_turf(src)) //Machine specific stuff at the end stored_material[MAT_STEEL] -= body_cost @@ -470,7 +294,7 @@ var/blur_amount var/confuse_amount - var/mob/living/carbon/human/occupant = null + VAR_PRIVATE/datum/weakref/weakref_occupant = null var/connected = null var/sleevecards = 2 @@ -487,6 +311,18 @@ RefreshParts() update_icon() +/obj/machinery/transhuman/resleever/proc/set_occupant(var/mob/living/carbon/human/H) + SHOULD_NOT_OVERRIDE(TRUE) + if(!H) + weakref_occupant = null + return + weakref_occupant = WEAKREF(H) + +/obj/machinery/transhuman/resleever/proc/get_occupant() + RETURN_TYPE(/mob/living/carbon/human) + SHOULD_NOT_OVERRIDE(TRUE) + return weakref_occupant?.resolve() + /obj/machinery/transhuman/resleever/RefreshParts() var/scan_rating = 0 for(var/obj/item/stock_parts/scanning_module/SM in component_parts) @@ -513,14 +349,15 @@ /obj/machinery/transhuman/resleever/tgui_data(mob/user) var/list/data = list() - data["occupied"] = !!occupant - if(occupant) - data["name"] = occupant.name - data["health"] = occupant.health - data["maxHealth"] = occupant.getMaxHealth() - data["stat"] = occupant.stat - data["mindStatus"] = !!occupant.mind - data["mindName"] = occupant.mind?.name + var/mob/living/carbon/human/H = get_occupant() + data["occupied"] = !!H + if(H) + data["name"] = H.name + data["health"] = H.health + data["maxHealth"] = H.getMaxHealth() + data["stat"] = H.stat + data["mindStatus"] = !!H.mind + data["mindName"] = H.mind?.name return data /obj/machinery/transhuman/resleever/attackby(obj/item/W, mob/user) @@ -583,6 +420,7 @@ add_fingerprint(user) /obj/machinery/transhuman/resleever/proc/putmind(var/datum/transhuman/mind_record/MR, mode = 1, var/mob/living/carbon/human/override = null, var/db_key) + var/mob/living/carbon/human/occupant = get_occupant() if((!occupant || !istype(occupant) || occupant.stat >= DEAD) && mode == 1) return 0 @@ -665,13 +503,14 @@ return 1 /obj/machinery/transhuman/resleever/proc/go_out(var/mob/M) - if(!( src.occupant )) + var/mob/living/carbon/human/occupant = get_occupant() + if(!occupant) return - if (src.occupant.client) - src.occupant.client.eye = src.occupant.client.mob - src.occupant.client.perspective = MOB_PERSPECTIVE - src.occupant.loc = src.loc - src.occupant = null + if (occupant.client) + occupant.client.eye = occupant.client.mob + occupant.client.perspective = MOB_PERSPECTIVE + occupant.forceMove(get_turf(src)) + set_occupant(null) icon_state = "implantchair" return @@ -679,7 +518,7 @@ if(!ishuman(M)) to_chat(usr, span_warning("\The [src] cannot hold this!")) return - if(src.occupant) + if(get_occupant()) to_chat(usr, span_warning("\The [src] is already occupied!")) return if(M.client) @@ -687,7 +526,7 @@ M.client.eye = src M.stop_pulling() M.loc = src - src.occupant = M + set_occupant(M) src.add_fingerprint(usr) icon_state = "implantchair_on" return 1 diff --git a/code/modules/tgui/modules/appearance_changer.dm b/code/modules/tgui/modules/appearance_changer.dm index 521a995252..3f528fc416 100644 --- a/code/modules/tgui/modules/appearance_changer.dm +++ b/code/modules/tgui/modules/appearance_changer.dm @@ -44,6 +44,7 @@ var/list/valid_wingstyles = list() var/list/valid_gradstyles = list() var/list/markings = null + var/cooldown //Anti-spam. If spammed, this can be REALLY laggy. /datum/tgui_module/appearance_changer/New( host, @@ -60,7 +61,7 @@ cam_screen.name = "screen" cam_screen.assigned_map = map_name cam_screen.del_on_map_removal = FALSE - cam_screen.screen_loc = "[map_name]:1,1" + cam_screen.screen_loc = "[map_name]:3:-32,3:-48" cam_plane_masters = get_tgui_plane_masters() @@ -83,11 +84,19 @@ whitelist = species_whitelist blacklist = species_blacklist +/datum/tgui_module/appearance_changer/proc/jiggle_map() + // Fix for weird byond bug, jiggles the map around a little + sleep(0.1 SECONDS) + cam_screen.screen_loc = "[map_name]:1,1" + sleep(0.1 SECONDS) + cam_screen.screen_loc = "[map_name]:3:-32,3:-48" // Align for larger icons and scales + /datum/tgui_module/appearance_changer/tgui_close(mob/user) . = ..() if(owner == user || !customize_usr) close_ui() UnregisterSignal(owner, COMSIG_OBSERVER_MOVED) + SEND_SIGNAL(owner, COMSIG_HUMAN_DNA_FINALIZED) // Update any components using our saved appearance owner = null last_camera_turf = null cut_data() @@ -101,6 +110,11 @@ /datum/tgui_module/appearance_changer/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) if(..()) return TRUE + if(cooldown > world.time) + to_chat(ui.user, span_warning("You are changing appearance too fast!")) + return FALSE + else + cooldown = world.time + 0.5 SECONDS var/obj/machinery/computer/transhuman/designer/DC = null var/datum/tgui_module/appearance_changer/body_designer/BD = null @@ -435,9 +449,8 @@ if (owner.change_marking_color(mark_datum, marking_color)) return TRUE if("rotate_view") - if(can_change(owner, APPEARANCE_RACE)) - owner.set_dir(turn(owner.dir, 90)) - return TRUE + owner.set_dir(turn(owner.dir, 90)) + return TRUE if("rename") if(owner) var/raw_name = tgui_input_text(ui.user, "Choose the a name:", "Sleeve Name") @@ -465,35 +478,30 @@ owner.custom_species = new_name return TRUE if("base_icon") - if(owner.species.selects_bodytype == SELECTS_BODYTYPE_FALSE) - var/datum/species/S = GLOB.all_species[owner.species.name] - owner.species.base_species = S.base_species // Return to original form - generate_data(ui.user, owner) - changed_hook(APPEARANCECHANGER_CHANGED_RACE) - return TRUE - var/list/choices - var/datum/species/S = GLOB.all_species[owner.species.name] - if(S.selects_bodytype == SELECTS_BODYTYPE_SHAPESHIFTER) - choices = S.get_valid_shapeshifter_forms() - else if(S.selects_bodytype == SELECTS_BODYTYPE_CUSTOM) - choices = GLOB.custom_species_bases - var/new_species = tgui_input_list(ui.user, "Please select basic shape.", "Body Shape", choices) - if(new_species && can_change(owner, APPEARANCE_RACE)) - owner.species.base_species = new_species - owner.regenerate_icons() - generate_data(ui.user, owner) - changed_hook(APPEARANCECHANGER_CHANGED_RACE) - return TRUE - if("blood_reagent") - var/new_blood_reagents = tgui_input_list(ui.user, "Please select blood restoration reagent:", "Character Preference", valid_bloodreagents) - if(new_blood_reagents && can_change(owner, APPEARANCE_RACE)) - owner.dna.blood_reagents = new_blood_reagents - changed_hook(APPEARANCECHANGER_CHANGED_RACE) - return TRUE + if(can_change(owner, APPEARANCE_MISC)) + var/new_species = tgui_input_list(ui.user, "Please select basic shape.", "Body Shape", GLOB.custom_species_bases) + if(new_species) + owner.species.base_species = new_species + owner.species.icobase = owner.species.get_icobase() + owner.species.deform = owner.species.get_icobase(get_deform = TRUE) + owner.species.vanity_base_fit = new_species + if(istype(owner.species, /datum/species/shapeshifter)) //TODO: See if this is still needed. + wrapped_species_by_ref["\ref[owner]"] = new_species + owner.regenerate_icons() + generate_data(ui.user, owner) + changed_hook(APPEARANCECHANGER_CHANGED_RACE) + return TRUE + if("blood_reagent") //you know, this feels REALLY odd to be able to change at will but WHATEVER, WE BALL. + if(can_change(owner, APPEARANCE_MISC)) + var/new_blood_reagents = tgui_input_list(ui.user, "Please select blood restoration reagent:", "Character Preference", valid_bloodreagents) + if(new_blood_reagents) + owner.dna.blood_reagents = new_blood_reagents + changed_hook(APPEARANCECHANGER_CHANGED_RACE) + return TRUE if("blood_color") var/current = owner.species.blood_color ? owner.species.blood_color : "#A10808" - var/blood_col = tgui_color_picker(ui.user, "Please select marking color", "Marking color", current) - if(blood_col && can_change(owner, APPEARANCE_RACE)) + var/blood_col = tgui_color_picker(ui.user, "Please select blood color", "Blood color", current) + if(blood_col && can_change(owner, APPEARANCE_MISC)) owner.dna.blood_color = blood_col changed_hook(APPEARANCECHANGER_CHANGED_RACE) return TRUE @@ -501,9 +509,9 @@ var/new_weight = tgui_input_number(ui.user, "Choose tbe character's relative body weight.\n\ This measurement should be set relative to a normal 5'10'' person's body and not the actual size of the character.\n\ ([WEIGHT_MIN]-[WEIGHT_MAX])", "Character Preference", null, WEIGHT_MAX, WEIGHT_MIN, round_value=FALSE) - if(new_weight && can_change(owner, APPEARANCE_RACE)) + if(new_weight && can_change(owner, APPEARANCE_MISC)) var/unit_of_measurement = tgui_alert(ui.user, "Is that number in pounds (lb) or kilograms (kg)?", "Confirmation", list("Pounds", "Kilograms")) - if(unit_of_measurement && can_change(owner, APPEARANCE_RACE)) + if(unit_of_measurement) if(unit_of_measurement == "Pounds") new_weight = round(text2num(new_weight),4) if(unit_of_measurement == "Kilograms") @@ -513,7 +521,7 @@ return TRUE if("size_scale") var/new_size = tgui_input_number(ui.user, "Choose size, ranging from [RESIZE_MINIMUM * 100]% to [RESIZE_MAXIMUM * 100]%", "Set Size", null, RESIZE_MAXIMUM * 100, RESIZE_MINIMUM * 100) - if(new_size && ISINRANGE(new_size,RESIZE_MINIMUM * 100,RESIZE_MAXIMUM * 100) && can_change(owner, APPEARANCE_RACE)) + if(new_size && ISINRANGE(new_size,RESIZE_MINIMUM * 100,RESIZE_MAXIMUM * 100) && can_change(owner, APPEARANCE_MISC)) owner.size_multiplier = new_size / 100 owner.update_transform(TRUE) owner.regenerate_icons() @@ -521,21 +529,21 @@ changed_hook(APPEARANCECHANGER_CHANGED_RACE) return TRUE if("scale_appearance") - if(can_change(owner, APPEARANCE_RACE)) + if(can_change(owner, APPEARANCE_MISC)) owner.dna.scale_appearance = !owner.dna.scale_appearance owner.fuzzy = owner.dna.scale_appearance owner.regenerate_icons() owner.set_dir(owner.dir) // Causes a visual update for fuzzy/offset return TRUE if("offset_override") - if(can_change(owner, APPEARANCE_RACE)) + if(can_change(owner, APPEARANCE_MISC)) owner.dna.offset_override = !owner.dna.offset_override owner.offset_override = owner.dna.offset_override owner.regenerate_icons() owner.set_dir(owner.dir) // Causes a visual update for fuzzy/offset return TRUE if("digitigrade") - if(can_change(owner, APPEARANCE_RACE)) + if(can_change(owner, APPEARANCE_MISC)) owner.dna.digitigrade = !owner.dna.digitigrade owner.digitigrade = owner.dna.digitigrade owner.regenerate_icons() @@ -545,28 +553,37 @@ if("species_sound") var/list/possible_species_sound_types = species_sound_map var/choice = tgui_input_list(ui.user, "Which set of sounds would you like to use? (Cough, Sneeze, Scream, Pain, Gasp, Death)", "Species Sounds", possible_species_sound_types) - if(choice && can_change(owner, APPEARANCE_RACE)) + if(choice && can_change(owner, APPEARANCE_MISC)) owner.species.species_sounds = choice return TRUE if("flavor_text") var/select_key = params["target"] - if(select_key && can_change(owner, APPEARANCE_RACE)) + if(select_key && can_change(owner, APPEARANCE_MISC)) if(select_key in owner.flavor_texts) switch(select_key) if("general") var/msg = strip_html_simple(tgui_input_text(ui.user,"Give a general description of the character. This will be shown regardless of clothings. Put in \"!clear\" to make blank.","Flavor Text",html_decode(owner.flavor_texts[select_key]), multiline = TRUE, prevent_enter = TRUE)) - if(can_change(owner, APPEARANCE_RACE)) // allows empty to wipe flavor + if(can_change(owner, APPEARANCE_MISC)) // allows empty to wipe flavor if(msg == "!clear") msg = "" owner.flavor_texts[select_key] = msg return TRUE else var/msg = strip_html_simple(tgui_input_text(ui.user,"Set the flavor text for their [select_key]. Put in \"!clear\" to make blank.","Flavor Text",html_decode(owner.flavor_texts[select_key]), multiline = TRUE, prevent_enter = TRUE)) - if(can_change(owner, APPEARANCE_RACE)) // allows empty to wipe flavor + if(can_change(owner, APPEARANCE_MISC)) // allows empty to wipe flavor if(msg == "!clear") msg = "" owner.flavor_texts[select_key] = msg return TRUE + if("load_saveslot") //saveslot_load + if(can_change(owner, APPEARANCE_ALL_COSMETIC)) + if(tgui_alert(owner, "Are you certain you wish to load the currently selected savefile?", "Load Savefile", list("No","Yes")) == "Yes") + if(owner && owner.client) //sanity + owner.client.prefs.vanity_copy_to(owner, FALSE, TRUE, FALSE, FALSE) + return TRUE + return TRUE + else + return TRUE // *********************************** // Body designer UI // *********************************** @@ -618,6 +635,7 @@ if(DC.disk.stored) qdel_null(DC.disk.stored) to_chat(ui.user,span_notice("\The [owner]'s bodyrecord was saved to the disk.")) + owner.update_dna() DC.disk.stored = new /datum/transhuman/body_record(owner, FALSE, FALSE) // Saves a COPY! DC.disk.stored.locked = FALSE // remove lock DC.disk.name = "[initial(DC.disk.name)] ([owner.real_name])" @@ -660,6 +678,7 @@ // Open UI ui = new(user, src, tgui_id, name) ui.open() + CallAsync(src, PROC_REF(jiggle_map)) if(custom_state) ui.set_state(custom_state) update_active_camera_screen() @@ -747,6 +766,7 @@ stock_bodyrecords_list_ui += N data["stock_records"] = stock_bodyrecords_list_ui data["change_race"] = can_change(owner, APPEARANCE_RACE) + data["change_misc"] = can_change(owner, APPEARANCE_MISC) data["gender_id"] = can_change(owner, APPEARANCE_GENDER) data["change_gender"] = can_change(owner, APPEARANCE_GENDER) data["change_hair"] = can_change(owner, APPEARANCE_HAIR) @@ -790,6 +810,8 @@ data["gender"] = owner.gender data["gender_id"] = owner.identifying_gender //This is saved to your MIND. data["change_race"] = can_change(owner, APPEARANCE_RACE) + data["saveslot_load"] = can_change(owner, APPEARANCE_ALL_COSMETIC) + data["change_misc"] = can_change(owner, APPEARANCE_MISC) data["change_gender"] = can_change(owner, APPEARANCE_GENDER) if(data["change_gender"]) @@ -873,7 +895,7 @@ local_skybox.cut_overlays() /datum/tgui_module/appearance_changer/proc/update_dna(mob/living/carbon/human/target) - if(target && (flags & APPEARANCE_UPDATE_DNA)) + if(target) target.update_dna() /datum/tgui_module/appearance_changer/proc/can_change(mob/living/carbon/human/target, var/flag) @@ -1038,7 +1060,7 @@ // ******************************************************* /datum/tgui_module/appearance_changer/cocoon name ="Appearance Editor (Cocoon)" - flags = APPEARANCE_ALL_HAIR | APPEARANCE_EYE_COLOR | APPEARANCE_SKIN + flags = APPEARANCE_ALL_COSMETIC customize_usr = TRUE /datum/tgui_module/appearance_changer/cocoon/tgui_status(mob/user, datum/tgui_state/state) @@ -1051,7 +1073,7 @@ // ******************************************************* /datum/tgui_module/appearance_changer/superpower name ="Appearance Editor (Superpower)" - flags = APPEARANCE_ALL_HAIR | APPEARANCE_EYE_COLOR | APPEARANCE_SKIN + flags = APPEARANCE_ALL_COSMETIC customize_usr = TRUE /datum/tgui_module/appearance_changer/superpower/tgui_status(mob/user, datum/tgui_state/state) @@ -1060,6 +1082,19 @@ return STATUS_CLOSE return ..() +// ******************************************************* +// Innate Species Transformation. +// ******************************************************* +/datum/tgui_module/appearance_changer/innate + name ="Appearance Editor (Innate)" + flags = APPEARANCE_ALL_COSMETIC + customize_usr = TRUE + +/datum/tgui_module/appearance_changer/innate/tgui_status(mob/user, datum/tgui_state/state) + if(owner.stat != CONSCIOUS) + return STATUS_CLOSE + return ..() + // ******************************************************* // Body design console // ******************************************************* @@ -1097,52 +1132,10 @@ if(owner) UnregisterSignal(owner, COMSIG_OBSERVER_MOVED) qdel_null(owner) - //Get the DNA and generate a new mob - var/datum/dna2/record/R = current_project.mydna - owner = new /mob/living/carbon/human(src, R.dna.species) - //Fix the external organs - for(var/part in current_project.limb_data) - var/status = current_project.limb_data[part] - if(status == null) continue //Species doesn't have limb? Child of amputated limb? - var/obj/item/organ/external/O = owner.organs_by_name[part] - if(!O) continue //Not an organ. Perhaps another amputation removed it already. - if(status == 1) //Normal limbs - continue - else if(status == 0) //Missing limbs - O.remove_rejuv() - else if(status) //Anything else is a manufacturer - O.remove_rejuv() //Don't robotize them, leave them removed so robotics can attach a part. - for(var/part in current_project.organ_data) - var/status = current_project.organ_data[part] - if(status == null) continue //Species doesn't have organ? Child of missing part? - var/obj/item/organ/I = owner.internal_organs_by_name[part] - if(!I) continue//Not an organ. Perhaps external conversion changed it already? - if(status == 0) //Normal organ - continue - else if(status == 1) //Assisted organ - I.mechassist() - else if(status == 2) //Mechanical organ - I.robotize() - else if(status == 3) //Digital organ - I.digitize() - //Set the name or generate one - owner.real_name = R.dna.real_name - owner.name = owner.real_name - //Apply DNA - owner.dna = R.dna.Clone() - owner.original_player = current_project.ckey - //Apply legs - owner.digitigrade = R.dna.digitigrade // ensure clone mob has digitigrade var set appropriately - if(owner.dna.digitigrade <> R.dna.digitigrade) - owner.dna.digitigrade = R.dna.digitigrade // ensure cloned DNA is set appropriately from record??? for some reason it doesn't get set right despite the override to datum/dna/Clone() - //Update appearance, remake icons - owner.UpdateAppearance() - owner.sync_dna_traits(FALSE) - owner.sync_organ_dna() - owner.initialize_vessel() - owner.dna.blood_reagents = R.dna.blood_reagents - owner.dna.blood_color = R.dna.blood_color - owner.regenerate_icons() + owner = current_project.produce_human_mob(src,FALSE,FALSE,"Designer [rand(999)]") + // Update some specifics from the current record + owner.dna.blood_reagents = current_project.mydna.dna.blood_reagents + owner.dna.blood_color = current_project.mydna.dna.blood_color owner.flavor_texts = current_project.mydna.flavor.Copy() owner.resize(current_project.sizemult, FALSE) owner.appearance_flags = current_project.aflags diff --git a/code/modules/vore/eating/inbelly_spawn.dm b/code/modules/vore/eating/inbelly_spawn.dm index f7e6ac273b..b4a674c1c3 100644 --- a/code/modules/vore/eating/inbelly_spawn.dm +++ b/code/modules/vore/eating/inbelly_spawn.dm @@ -169,12 +169,12 @@ Please do not abuse this ability. var/datum/language/keylang = GLOB.all_languages[prey.prefs.language_custom_keys[key]] if(keylang) new_character.language_keys[key] = keylang - // VOREStation Add: Preferred Language Setting; if(prey.prefs.preferred_language) // Do we have a preferred language? var/datum/language/def_lang = GLOB.all_languages[prey.prefs.preferred_language] if(def_lang) new_character.default_language = def_lang - // VOREStation Add End + + SEND_SIGNAL(new_character, COMSIG_HUMAN_DNA_FINALIZED) new_character.regenerate_icons() diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx index 16ed991fc6..88e763f363 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx @@ -1,5 +1,5 @@ import { useBackend } from 'tgui/backend'; -import { Button, ColorBox, LabeledList, Section } from 'tgui-core/components'; +import { Button, LabeledList, Section } from 'tgui-core/components'; import type { Data, species } from './types'; @@ -35,65 +35,8 @@ export const AppearanceChangerSpecies = (props) => { ) : ( '' )} - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
); }; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerMisc.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerMisc.tsx new file mode 100644 index 0000000000..36e485017a --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerMisc.tsx @@ -0,0 +1,97 @@ +import { useBackend } from 'tgui/backend'; +import { + Button, + ColorBox, + LabeledList, + Section, + Stack, +} from 'tgui-core/components'; + +import type { Data } from './types'; + +export const AppearanceChangerMisc = (props) => { + const { act, data } = useBackend(); + const { specimen } = data; + return ( +
+ +
+ + + +
+
+ + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + +
+
+ + + + + + + + + + + + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/constants.ts b/tgui/packages/tgui/interfaces/AppearanceChanger/constants.ts index 86d040fa31..eac1806a8b 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/constants.ts +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/constants.ts @@ -16,4 +16,5 @@ export const TAB_EARS2 = 7; export const TAB_TAIL = 8; export const TAB_WINGS = 9; export const TAB_MARKINGS = 10; +export const TAB_MISC = 11; export const MARKINGS_PER_PAGE = 30; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx index 78b581621e..443187be8d 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx @@ -21,6 +21,7 @@ import { AppearanceChangerColors } from './AppearanceChangerColors'; import { AppearanceChangerFlavor } from './AppearanceChangerFlavor'; import { AppearanceChangerHeader } from './AppearanceChangerHeader'; import { AppearanceChangerMarkings } from './AppearanceChangerMarkings'; +import { AppearanceChangerMisc } from './AppearanceChangerMisc'; import { AppearanceChangerHair, AppearanceChangerParts, @@ -34,6 +35,7 @@ import { TAB_GENDER, TAB_HAIR, TAB_MARKINGS, + TAB_MISC, TAB_RACE, TAB_TAIL, TAB_WINGS, @@ -60,6 +62,7 @@ export const AppearanceChanger = (props) => { wing_style, wing_styles, change_race, + change_misc, change_gender, change_eye_color, change_skin_tone, @@ -90,7 +93,7 @@ export const AppearanceChanger = (props) => { ) : ( ); - tab[TAB_FLAVOR] = change_race ? ( + tab[TAB_FLAVOR] = change_misc ? ( ) : ( @@ -180,6 +183,11 @@ export const AppearanceChanger = (props) => { ) : ( ); + tab[TAB_MISC] = change_misc ? ( + + ) : ( + + ); let firstAccesibleTab = -1; if (change_race) { @@ -378,6 +386,14 @@ export const AppearanceChanger = (props) => { ) : null} + {change_misc ? ( + setTabIndex(TAB_MISC)} + > + Misc + + ) : null} {tab[tabIndex]} diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts index 70a536d51a..8e3373b944 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts @@ -17,6 +17,7 @@ export type Data = { tail_styles: styles[]; markings: { marking_name: string; marking_color: string }[]; change_race: BooleanLike; + change_misc: BooleanLike; change_gender: BooleanLike; genders: genders; id_genders: genders; diff --git a/vorestation.dme b/vorestation.dme index b2bcbe518c..ebad5cab0c 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -179,6 +179,7 @@ #include "code\__defines\typeids.dm" #include "code\__defines\unit_tests.dm" #include "code\__defines\update_icons.dm" +#include "code\__defines\var_copy.dm" #include "code\__defines\vchatlog.dm" #include "code\__defines\verb_manager.dm" #include "code\__defines\visualnet.dm"