diff --git a/_maps/RandomZLevels/research.dmm b/_maps/RandomZLevels/research.dmm
index 26ac66e98a..ec744be56d 100644
--- a/_maps/RandomZLevels/research.dmm
+++ b/_maps/RandomZLevels/research.dmm
@@ -1360,7 +1360,7 @@
/obj/structure/closet/crate,
/obj/item/disk/data{
desc = "A data disk used to store cloning and genetic records. The name on the label appears to be scratched off.";
- fields = list("label" = "Buffer1:Kr-$$@##", "UI" = "f8f603857000f930127c4", "SE" = "414401462231053131010241514651403453121613263463440351136366", "UE" = "340008485c321e542aed4df7032ac04d", "name" = "Krystal Symers", "blood_type" = "A+");
+ genetic_makeup_buffer = list("label" = "Buffer1:Kr-$$@##", "UI" = "f8f603857000f930127c4", "SE" = "414401462231053131010241514651403453121613263463440351136366", "UE" = "340008485c321e542aed4df7032ac04d", "name" = "Krystal Symers", "blood_type" = "A+");
name = "dusty genetics data disk";
read_only = 1
},
@@ -1749,7 +1749,7 @@
/obj/structure/closet/crate,
/obj/item/disk/data{
desc = "A data disk used to store cloning and genetic records. The name on the label appears to be scratched off with the words 'DO NOT CLONE' hastily written over it.";
- fields = list("label" = "Buffer1:George Melons", "UI" = "3c300f11b5421ca7014d8", "SE" = "430431205660551642142504334461413202111310233445620533134255", "UE" = "6893e6a0b0076a41897776b10cc2b324", "name" = "George Melons", "blood_type" = "B+");
+ genetic_makeup_buffer = list("label" = "Buffer1:George Melons", "UI" = "3c300f11b5421ca7014d8", "SE" = "430431205660551642142504334461413202111310233445620533134255", "UE" = "6893e6a0b0076a41897776b10cc2b324", "name" = "George Melons", "blood_type" = "B+");
name = "old genetics data disk"
},
/obj/item/disk/data{
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index e08d5c509c..a1cb1b3eeb 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -15,6 +15,7 @@
var/mob/living/holder
var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875
var/mutation_index[DNA_MUTATION_BLOCKS] //List of which mutations this carbon has and its assigned block
+ var/default_mutation_genes[DNA_MUTATION_BLOCKS] //List of the default genes from this mutation to allow DNA Scanner highlighting
var/stability = 100
var/scrambled = FALSE //Did we take something like mutagen? In that case we cant get our genes scanned to instantly cheese all the powers.
var/skin_tone_override //because custom skin tones are not found in the skin_tones global list.
@@ -58,6 +59,7 @@
H.give_genitals(TRUE)//This gives the body the genitals of this DNA. Used for any transformations based on DNA
if(transfer_SE)
destination.dna.mutation_index = mutation_index
+ destination.dna.default_mutation_genes = default_mutation_genes
destination.dna.update_body_size(old_size)
@@ -66,6 +68,7 @@
/datum/dna/proc/copy_dna(datum/dna/new_dna)
new_dna.unique_enzymes = unique_enzymes
new_dna.mutation_index = mutation_index
+ new_dna.default_mutation_genes = default_mutation_genes
new_dna.uni_identity = uni_identity
new_dna.blood_type = blood_type
new_dna.skin_tone_override = skin_tone_override
@@ -159,15 +162,18 @@
if(!LAZYLEN(mutations_temp))
return
mutation_index.Cut()
+ default_mutation_genes.Cut()
shuffle_inplace(mutations_temp)
if(ismonkey(holder))
mutations |= new RACEMUT(MUT_NORMAL)
mutation_index[RACEMUT] = GET_SEQUENCE(RACEMUT)
else
mutation_index[RACEMUT] = create_sequence(RACEMUT, FALSE)
+ default_mutation_genes[RACEMUT] = mutation_index[RACEMUT]
for(var/i in 2 to DNA_MUTATION_BLOCKS)
var/datum/mutation/human/M = mutations_temp[i]
- mutation_index[M.type] = create_sequence(M.type, FALSE,M.difficulty)
+ mutation_index[M.type] = create_sequence(M.type, FALSE, M.difficulty)
+ default_mutation_genes[M.type] = mutation_index[M.type]
shuffle_inplace(mutation_index)
//Used to generate original gene sequences for every mutation
@@ -372,8 +378,8 @@
return dna
-/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, newreal_name, newblood_type, datum/species/mrace, newfeatures)
-
+/mob/living/carbon/human/proc/hardset_dna(ui, list/mutation_index, list/default_mutation_genes, newreal_name, newblood_type, datum/species/mrace, newfeatures, list/mutations, force_transfer_mutations)
+//Do not use force_transfer_mutations for stuff like cloners without some precautions, otherwise some conditional mutations could break (timers, drill hat etc)
if(newfeatures)
var/old_size = dna.features["body_size"]
dna.features = newfeatures
@@ -397,6 +403,10 @@
if(LAZYLEN(mutation_index))
dna.mutation_index = mutation_index.Copy()
+ if(LAZYLEN(default_mutation_genes))
+ dna.default_mutation_genes = default_mutation_genes.Copy()
+ else
+ dna.default_mutation_genes = mutation_index.Copy()
domutcheck()
SEND_SIGNAL(src, COMSIG_HUMAN_HARDSET_DNA, ui, mutation_index, newreal_name, newblood_type, mrace, newfeatures)
@@ -488,8 +498,11 @@
. = TRUE
if(on)
mutation_index[HM.type] = GET_SEQUENCE(HM.type)
+ default_mutation_genes[HM.type] = mutation_index[HM.type]
else if(GET_SEQUENCE(HM.type) == mutation_index[HM.type])
mutation_index[HM.type] = create_sequence(HM.type, FALSE, HM.difficulty)
+ default_mutation_genes[HM.type] = mutation_index[HM.type]
+
/datum/dna/proc/activate_mutation(mutation) //note that this returns a boolean and not a new mob
if(!mutation)
diff --git a/code/datums/mutations/_mutations.dm b/code/datums/mutations/_mutations.dm
index 9edaa7605c..ddcf80ae5d 100644
--- a/code/datums/mutations/_mutations.dm
+++ b/code/datums/mutations/_mutations.dm
@@ -191,3 +191,23 @@
power.panel = "Genetic"
owner.AddSpell(power)
return TRUE
+
+// Runs through all the coefficients and uses this to determine which chromosomes the
+// mutation can take. Stores these as text strings in a list.
+/datum/mutation/human/proc/update_valid_chromosome_list()
+ valid_chrom_list.Cut()
+
+ if(can_chromosome == CHROMOSOME_NEVER)
+ valid_chrom_list += "none"
+ return
+
+ valid_chrom_list += "Reinforcement"
+
+ if(stabilizer_coeff != -1)
+ valid_chrom_list += "Stabilizer"
+ if(synchronizer_coeff != -1)
+ valid_chrom_list += "Synchronizer"
+ if(power_coeff != -1)
+ valid_chrom_list += "Power"
+ if(energy_coeff != -1)
+ valid_chrom_list += "Energetic"
diff --git a/code/game/objects/items/chromosome.dm b/code/game/objects/items/chromosome.dm
index 1340e7f35b..8330a8e202 100644
--- a/code/game/objects/items/chromosome.dm
+++ b/code/game/objects/items/chromosome.dm
@@ -53,32 +53,32 @@
/obj/item/chromosome/stabilizer
name = "stabilizer chromosome"
- desc = "A chromosome that adjusts to the body to reduce genetic damage by 20%."
+ desc = "A chromosome that reduces mutation instability by 20%."
icon_state = "stabilizer"
stabilizer_coeff = 0.8
weight = 1
/obj/item/chromosome/synchronizer
name = "synchronizer chromosome"
- desc = "A chromosome that gives the mind more controle over the mutation, reducing knockback and downsides by 50%."
+ desc = "A chromosome that reduces mutation knockback and downsides by 50%."
icon_state = "synchronizer"
synchronizer_coeff = 0.5
/obj/item/chromosome/power
name = "power chromosome"
- desc = "A power chromosome for boosting certain mutation's power by 50%."
+ desc = "A chromosome that increases mutation power by 50%."
icon_state = "power"
power_coeff = 1.5
/obj/item/chromosome/energy
name = "energetic chromosome"
- desc = "A chromosome that reduces cooldown on action based mutations by 50%."
+ desc = "A chromosome that reduces action based mutation cooldowns by by 50%."
icon_state = "energy"
energy_coeff = 0.5
/obj/item/chromosome/reinforcer
name = "reinforcement chromosome"
- desc = "Renders the mutation immune to mutadone."
+ desc = "A chromosome that renders mutations immune to mutadone."
icon_state = "reinforcer"
weight = 3
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index ef8a63a728..733eb187fd 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -421,7 +421,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
new_character.real_name = record_found.fields["name"]
new_character.gender = record_found.fields["gender"]
new_character.age = record_found.fields["age"]
- new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"])
+ new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], null, record_found.fields["name"], record_found.fields["blood_type"], new record_found.fields["species"], record_found.fields["features"])
else
var/datum/preferences/A = new()
A.copy_to(new_character)
diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm
index aeddf771b8..78fddbd989 100644
--- a/code/modules/hydroponics/grown/replicapod.dm
+++ b/code/modules/hydroponics/grown/replicapod.dm
@@ -115,7 +115,7 @@
features["mcolor"] = "#59CE00"
for(var/V in quirks)
new V(podman)
- podman.hardset_dna(null,null,podman.real_name,blood_type, new /datum/species/pod,features)//Discard SE's and UI's, podman cloning is inaccurate, and always make them a podman
+ podman.hardset_dna(null,null,null,podman.real_name,blood_type, new /datum/species/pod,features)//Discard SE's and UI's, podman cloning is inaccurate, and always make them a podman
podman.set_cloned_appearance()
else //else, one packet of seeds. maybe two
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 689ab56842..cf81d5f64f 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -63,3 +63,11 @@
var/damageoverlaytemp = 0
var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects
+
+ /// Protection (insulation) from the heat, Value 0-1 corresponding to the percentage of protection
+ var/heat_protection = 0 // No heat protection
+ /// Protection (insulation) from the cold, Value 0-1 corresponding to the percentage of protection
+ var/cold_protection = 0 // No cold protection
+
+ /// Timer id of any transformation
+ var/transformation_timer
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 32e492f5fa..5e6aef75ca 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -362,7 +362,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
//keep it at the right spot, so we can't have people taking shortcuts
var/location = C.dna.mutation_index.Find(inert_mutation)
C.dna.mutation_index[location] = new_species.inert_mutation
+ C.dna.default_mutation_genes[location] = C.dna.mutation_index[location]
C.dna.mutation_index[new_species.inert_mutation] = create_sequence(new_species.inert_mutation)
+ C.dna.default_mutation_genes[new_species.inert_mutation] = C.dna.mutation_index[new_species.inert_mutation]
SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src)
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index 42aaa110c8..ebecf741f4 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -1,26 +1,8 @@
-/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG))
- if (notransform)
+#define TRANSFORMATION_DURATION 22
+
+/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
+ if (notransform || transformation_timer)
return
- //Handle items on mob
-
- //first implants & organs
- var/list/stored_implants = list()
- var/list/int_organs = list()
-
- if (tr_flags & TR_KEEPIMPLANTS)
- for(var/X in implants)
- var/obj/item/implant/IMP = X
- stored_implants += IMP
- IMP.removed(src, 1, 1)
-
- var/list/missing_bodyparts_zones = get_missing_limbs()
-
- var/obj/item/cavity_object
-
- var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST)
- if(CH.cavity_item)
- cavity_object = CH.cavity_item
- CH.cavity_item = null
if(tr_flags & TR_KEEPITEMS)
var/Itemlist = get_equipped_items(TRUE)
@@ -30,13 +12,36 @@
//Make mob invisible and spawn animation
notransform = TRUE
- Stun(INFINITY, ignore_canstun = TRUE)
+ Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
icon = null
cut_overlays()
invisibility = INVISIBILITY_MAXIMUM
new /obj/effect/temp_visual/monkeyify(loc)
- sleep(22)
+
+ transformation_timer = addtimer(CALLBACK(src, .proc/finish_monkeyize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE)
+
+/mob/living/carbon/proc/finish_monkeyize(tr_flags)
+ transformation_timer = null
+
+ var/list/missing_bodyparts_zones = get_missing_limbs()
+
+ var/list/stored_implants = list()
+
+ if (tr_flags & TR_KEEPIMPLANTS)
+ for(var/X in implants)
+ var/obj/item/implant/IMP = X
+ stored_implants += IMP
+ IMP.removed(src, 1, 1)
+
+ var/list/int_organs = list()
+ var/obj/item/cavity_object
+
+ var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST)
+ if(CH.cavity_item)
+ cavity_object = CH.cavity_item
+ CH.cavity_item = null
+
var/mob/living/carbon/monkey/O = new /mob/living/carbon/monkey( loc )
// hash the original name?
@@ -50,6 +55,7 @@
if(tr_flags & TR_KEEPSE)
O.dna.mutation_index = dna.mutation_index
+ O.dna.default_mutation_genes = dna.default_mutation_genes
O.dna.set_se(1, GET_INITIALIZED_MUTATION(RACEMUT))
if(suiciding)
@@ -149,12 +155,33 @@
////////////////////////// Humanize //////////////////////////////
//Could probably be merged with monkeyize but other transformations got their own procs, too
-/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG))
- if (notransform)
+/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
+ if (notransform || transformation_timer)
return
- //Handle items on mob
- //first implants & organs
+ //now the rest
+ if (tr_flags & TR_KEEPITEMS)
+ var/Itemlist = get_equipped_items(TRUE)
+ Itemlist += held_items
+ for(var/obj/item/W in Itemlist)
+ dropItemToGround(W, TRUE)
+ if (client)
+ client.screen -= W
+
+ //Make mob invisible and spawn animation
+ notransform = TRUE
+ Paralyze(TRANSFORMATION_DURATION, ignore_canstun = TRUE)
+
+ icon = null
+ cut_overlays()
+ invisibility = INVISIBILITY_MAXIMUM
+ new /obj/effect/temp_visual/monkeyify/humanify(loc)
+
+ transformation_timer = addtimer(CALLBACK(src, .proc/finish_humanize, tr_flags), TRANSFORMATION_DURATION, TIMER_UNIQUE)
+
+/mob/living/carbon/proc/finish_humanize(tr_flags)
+ transformation_timer = null
+
var/list/stored_implants = list()
var/list/int_organs = list()
@@ -173,25 +200,6 @@
cavity_object = CH.cavity_item
CH.cavity_item = null
- //now the rest
- if (tr_flags & TR_KEEPITEMS)
- var/Itemlist = get_equipped_items(TRUE)
- Itemlist += held_items
- for(var/obj/item/W in Itemlist)
- dropItemToGround(W, TRUE)
- if (client)
- client.screen -= W
-
-
-
- //Make mob invisible and spawn animation
- notransform = TRUE
- Stun(22, ignore_canstun = TRUE)
- icon = null
- cut_overlays()
- invisibility = INVISIBILITY_MAXIMUM
- new /obj/effect/temp_visual/monkeyify/humanify(loc)
- sleep(22)
var/mob/living/carbon/human/O = new( loc )
for(var/obj/item/C in O.loc)
O.equip_to_appropriate_slot(C)
@@ -208,6 +216,7 @@
if(tr_flags & TR_KEEPSE)
O.dna.mutation_index = dna.mutation_index
+ O.dna.default_mutation_genes = dna.default_mutation_genes
O.dna.set_se(0, GET_INITIALIZED_MUTATION(RACEMUT))
O.domutcheck()
@@ -580,3 +589,44 @@
. = new_mob
qdel(src)
+
+/* Certain mob types have problems and should not be allowed to be controlled by players.
+ *
+ * This proc is here to force coders to manually place their mob in this list, hopefully tested.
+ * This also gives a place to explain -why- players shouldnt be turn into certain mobs and hopefully someone can fix them.
+ */
+/mob/proc/safe_animal(MP)
+
+//Bad mobs! - Remember to add a comment explaining what's wrong with the mob
+ if(!MP)
+ return 0 //Sanity, this should never happen.
+
+ if(ispath(MP, /mob/living/simple_animal/hostile/construct))
+ return 0 //Verbs do not appear for players.
+
+//Good mobs!
+ if(ispath(MP, /mob/living/simple_animal/pet/cat))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/pet/dog/corgi))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/crab))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/hostile/carp))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/hostile/mushroom))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/shade))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/hostile/killertomato))
+ return 1
+ if(ispath(MP, /mob/living/simple_animal/mouse))
+ return 1 //It is impossible to pull up the player panel for mice (Fixed! - Nodrak)
+ if(ispath(MP, /mob/living/simple_animal/hostile/bear))
+ return 1 //Bears will auto-attack mobs, even if they're player controlled (Fixed! - Nodrak)
+ if(ispath(MP, /mob/living/simple_animal/parrot))
+ return 1 //Parrots are no longer unfinished! -Nodrak
+
+ //Not in here? Must be untested!
+ return 0
+
+#undef TRANSFORMATION_DURATION
diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm
index f6f56c049a..11518b236f 100644
--- a/code/modules/spells/spell_types/lichdom.dm
+++ b/code/modules/spells/spell_types/lichdom.dm
@@ -132,7 +132,7 @@
lich.real_name = mind.name
mind.transfer_to(lich)
mind.grab_ghost(force=TRUE)
- lich.hardset_dna(null,null,lich.real_name,null, new /datum/species/skeleton/space)
+ lich.hardset_dna(null,null,null,lich.real_name,null, new /datum/species/skeleton)
to_chat(lich, "Your bones clatter and shudder as you are pulled back into this world!")
var/turf/body_turf = get_turf(old_body)
lich.DefaultCombatKnockdown(200 + 200*resurrections)
diff --git a/tgui-next/packages/tgui-dev-server/reloader.js b/tgui-next/packages/tgui-dev-server/reloader.js
index d5541c2a99..394e55afdb 100644
--- a/tgui-next/packages/tgui-dev-server/reloader.js
+++ b/tgui-next/packages/tgui-dev-server/reloader.js
@@ -9,6 +9,8 @@ const logger = createLogger('reloader');
const HOME = os.homedir();
const SEARCH_LOCATIONS = [
+ // Custom location
+ process.env.BYOND_CACHE,
// Windows
`${HOME}/*/BYOND/cache`,
// Wine
@@ -28,6 +30,9 @@ export const findCacheRoot = async () => {
logger.log('looking for byond cache');
// Find BYOND cache folders
for (let pattern of SEARCH_LOCATIONS) {
+ if (!pattern) {
+ continue;
+ }
const paths = await resolveGlob(pattern);
if (paths.length > 0) {
cacheRoot = paths[0];
diff --git a/tgui-next/packages/tgui/components/Dimmer.js b/tgui-next/packages/tgui/components/Dimmer.js
index 9d3ead0549..f38d0a026a 100644
--- a/tgui-next/packages/tgui/components/Dimmer.js
+++ b/tgui-next/packages/tgui/components/Dimmer.js
@@ -1,19 +1,18 @@
+import { classes } from 'common/react';
import { Box } from './Box';
export const Dimmer = props => {
- const { style, ...rest } = props;
+ const { className, children, ...rest } = props;
return (