diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm
index 05338f347020c..5f1fe7ce47318 100644
--- a/code/__DEFINES/DNA.dm
+++ b/code/__DEFINES/DNA.dm
@@ -98,7 +98,7 @@
#define DNA_HAIR_STYLE_BLOCK 7
/// This number needs to equal the total number of DNA blocks
-#define DNA_FEATURE_BLOCKS 22
+#define DNA_FEATURE_BLOCKS 28
#define DNA_MUTANT_COLOR_BLOCK 1
#define DNA_ETHEREAL_COLOR_BLOCK 2
@@ -123,6 +123,12 @@
#define DNA_PRETERNIS_WEATHERING_BLOCK 20
#define DNA_PRETERNIS_ANTENNA_BLOCK 21
#define DNA_PRETERNIS_EYE_BLOCK 22
+#define DNA_VOX_QUILLS_BLOCK 23
+#define DNA_VOX_FACIAL_QUILLS_BLOCK 24
+#define DNA_VOX_TAIL_MARKINGS_BLOCK 25
+#define DNA_VOX_BODY_MARKINGS_BLOCK 26
+#define DNA_VOX_SKIN_TONE_BLOCK 27
+#define DNA_MUTANT_COLOR_SECONDARY 28
#define DNA_SEQUENCE_LENGTH 4
#define DNA_MUTATION_BLOCKS 8
@@ -183,6 +189,9 @@
/// has a tail
#define HAS_TAIL 24
#define NONANITES 25
+#define HAIRCOLOR 26
+#define FACEHAIRCOLOR 27
+#define MUTCOLORS_SECONDARY 28
//organ slots
#define ORGAN_SLOT_BRAIN "brain"
diff --git a/code/__DEFINES/species_clothing_paths.dm b/code/__DEFINES/species_clothing_paths.dm
new file mode 100644
index 0000000000000..b504e8594a806
--- /dev/null
+++ b/code/__DEFINES/species_clothing_paths.dm
@@ -0,0 +1,7 @@
+//HUMAN PATHS
+///The dmi for humanoid uniforms
+#define DEFAULT_UNIFORM_FILE 'icons/mob/clothing/uniform/uniform.dmi'
+///The dmi for humanoid shoes
+#define DEFAULT_SHOES_FILE 'icons/mob/clothing/feet/feet.dmi'
+///The dmi for humanoid oversuits
+#define DEFAULT_SUIT_FILE 'icons/mob/clothing/suit/suit.dmi'
diff --git a/code/__DEFINES/{yogs_defines}/colors.dm b/code/__DEFINES/{yogs_defines}/colors.dm
new file mode 100644
index 0000000000000..c8312ec5ccc9c
--- /dev/null
+++ b/code/__DEFINES/{yogs_defines}/colors.dm
@@ -0,0 +1,10 @@
+//Vox blood color
+#define COLOR_BLOOD_VOX "#2299FC"
+
+//Color blending modes
+#define COLOR_BLEND_MULTIPLY "multiply"
+#define COLOR_BLEND_ADD "add"
+
+//Color matrix
+#define COLOR_MATRIX_ADD(color) list(COLOR_RED, COLOR_VIBRANT_LIME, COLOR_BLUE, color)
+#define COLOR_MATRIX_OVERLAY(color) list(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, color)
diff --git a/code/__DEFINES/{yogs_defines}/is_helpers.dm b/code/__DEFINES/{yogs_defines}/is_helpers.dm
index c7f9e20420089..f16192ad56698 100644
--- a/code/__DEFINES/{yogs_defines}/is_helpers.dm
+++ b/code/__DEFINES/{yogs_defines}/is_helpers.dm
@@ -1,3 +1,5 @@
+#define isvox(A) (is_species(A, /datum/species/vox))
+
#define isdarkspawn(A) (A?.mind?.has_antag_datum(/datum/antagonist/darkspawn))
#define isthrall(A) (A?.mind?.has_antag_datum(/datum/antagonist/thrall))
#define ispsyche(A) (A?.mind?.has_antag_datum(/datum/antagonist/psyche)) //non thrall teammates
diff --git a/code/__DEFINES/{yogs_defines}/mobs.dm b/code/__DEFINES/{yogs_defines}/mobs.dm
index 816023c8161ac..dc4c11e90278d 100644
--- a/code/__DEFINES/{yogs_defines}/mobs.dm
+++ b/code/__DEFINES/{yogs_defines}/mobs.dm
@@ -8,3 +8,7 @@
#define REGEN_BLOOD_REQUIREMENT 40 // The amount of "blood" that a slimeperson consumes when regenerating a single limb.
#define MONKIFY_BLOOD_COEFFICIENT (BLOOD_VOLUME_MONKEY/BLOOD_VOLUME_GENERIC) //the ratio of monkey to human blood volume so a 100% blood volume monkey will not instantly die when you turn it into a human with ~58% blood volume
+
+#define SPECIES_VOX "vox"
+
+#define BUTT_SPRITE_VOX "vox"
diff --git a/code/__DEFINES/{yogs_defines}/species_clothing_paths.dm b/code/__DEFINES/{yogs_defines}/species_clothing_paths.dm
new file mode 100644
index 0000000000000..cb02eb5bf2680
--- /dev/null
+++ b/code/__DEFINES/{yogs_defines}/species_clothing_paths.dm
@@ -0,0 +1,11 @@
+//VOX PATHS
+#define VOX_MASK_FILE 'icons/mob/clothing/species/vox/mask.dmi'
+#define VOX_HEAD_FILE 'icons/mob/clothing/species/vox/head.dmi'
+#define VOX_BACK_FILE 'icons/mob/clothing/species/vox/back.dmi'
+#define VOX_EARS_FILE 'icons/mob/clothing/species/vox/ears.dmi'
+#define VOX_EYES_FILE 'icons/mob/clothing/species/vox/eyes.dmi'
+#define VOX_SHOES_FILE 'icons/mob/clothing/species/vox/shoes.dmi'
+#define VOX_GLOVES_FILE 'icons/mob/clothing/species/vox/gloves.dmi'
+#define VOX_HELMET_FILE 'icons/mob/clothing/species/vox/helmet.dmi'
+#define VOX_SUIT_FILE 'icons/mob/clothing/species/vox/suit.dmi'
+#define VOX_UNIFORM_FILE 'icons/mob/clothing/species/vox/uniform.dmi'
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index ab1c0a2c5ed81..3b538f1b8d9af 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -45,6 +45,15 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_screens, GLOB.ipc_screens_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_antennas, GLOB.ipc_antennas_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_chassis, GLOB.ipc_chassis_list)
+ // Vox bodyparts
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_quills, GLOB.vox_quills_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_facial_quills, GLOB.vox_facial_quills_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_tails, GLOB.vox_tails_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_body_markings, GLOB.vox_body_markings_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_tail_markings, GLOB.vox_tail_markings_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails_animated/vox, GLOB.animated_vox_tails_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_tail_markings_animated, GLOB.animated_vox_tail_markings_list)
+
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index f9f7109482573..773eab6e1cee8 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -228,10 +228,13 @@ world
/icon/proc/Greyify()
MapColors(0.75,0.3,0.3, 0.6,0.75,0.6, 0.10,0.10,0.50, 0,0,0)
-/icon/proc/ColorTone(tone)
- Greyify()
+/icon/proc/ColorTone(tone, greyify = FALSE)
+ if(greyify)
+ Greyify()
+ else
+ GrayScale()
- var/list/TONE = ReadRGB(tone)
+ var/list/TONE = rgb2num(tone)
var/gray = round(TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11, 1)
var/icon/upper = (255-gray) ? new(src) : null
@@ -922,7 +925,7 @@ world
/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created.
var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon.
- flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish.
+ flat_icon.ColorTone(rgb(125,180,225), greyify = TRUE)//Let's make it bluish.
flat_icon.ChangeOpacity(0.5)//Make it half transparent.
var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect.
flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect.
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 1ab59833e0f09..936ddf1b2e1f7 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -95,10 +95,21 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_antennas, GLOB.ipc_antennas_list)
if(!GLOB.ipc_chassis_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_chassis, GLOB.ipc_chassis_list)
+ if(!GLOB.vox_quills_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_quills, GLOB.vox_quills_list)
+ if(!GLOB.vox_facial_quills_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_facial_quills, GLOB.vox_facial_quills_list)
+ if(!GLOB.vox_tails_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_tails, GLOB.vox_tails_list)
+ if(!GLOB.vox_body_markings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_body_markings, GLOB.vox_body_markings_list)
+ if(!GLOB.vox_tail_markings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vox_tail_markings, GLOB.vox_tail_markings_list)
//For now we will always return none for tail_human and ears. this shit was unreadable if you do somethign like this make it at least readable
return(list(
"mcolor" = "#[pick("7F","FF")][pick("7F","FF")][pick("7F","FF")]",
+ "mcolor_secondary" = "#[pick("7F","FF")][pick("7F","FF")][pick("7F","FF")]",
"gradientstyle" = random_hair_gradient_style(10),
"gradientcolor" = "#[pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")]",
"tail_lizard" = pick(GLOB.tails_list_lizard),
@@ -126,7 +137,12 @@
"pod_hair" = pick(GLOB.pod_hair_list),
"ipc_screen" = pick(GLOB.ipc_screens_list),
"ipc_antenna" = pick(GLOB.ipc_antennas_list),
- "ipc_chassis" = pick(GLOB.ipc_chassis_list)
+ "ipc_chassis" = pick(GLOB.ipc_chassis_list),
+ "vox_skin_tone" = pick(GLOB.vox_skin_tones),
+ "vox_quills" = pick(GLOB.vox_quills_list),
+ "vox_facial_quills" = pick(GLOB.vox_facial_quills_list),
+ "vox_body_markings" = pick(GLOB.vox_body_markings_list),
+ "vox_tail_markings" = pick(GLOB.vox_tail_markings_list)
))
/proc/random_hair_style(gender)
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index 0bda2b22d1bce..0a879c2632648 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -80,12 +80,14 @@ GLOBAL_LIST_INIT(maintenance_loot_traditional,list(
/obj/item/clothing/mask/breath = W_UNCOMMON,
/obj/item/tank/internals/air = W_COMMON,
/obj/item/tank/internals/anesthetic = W_RARE,
+ /obj/item/tank/internals/nitrogen = W_RARE,
/obj/item/tank/internals/emergency_oxygen = W_UNCOMMON,
/obj/item/tank/internals/emergency_oxygen/empty = W_UNCOMMON,
/obj/item/tank/internals/emergency_oxygen/engi = W_RARE,
/obj/item/tank/internals/emergency_oxygen/engi/empty = W_RARE,
/obj/item/tank/internals/emergency_oxygen/double = W_MYTHICAL,
/obj/item/tank/internals/emergency_oxygen/double/empty = W_MYTHICAL,
+ /obj/item/tank/internals/emergency_oxygen/vox = W_MYTHICAL,
/obj/item/tank/internals/ipc_coolant = W_RARE,
/obj/item/tank/internals/ipc_coolant/empty = W_RARE,
/obj/item/tank/internals/plasma = W_LEGENDARY,
@@ -365,6 +367,7 @@ GLOBAL_LIST_INIT(maintenance_loot_traditional,list(
/obj/item/stack/sheet/animalhide/gorilla = W_RARE,
/obj/item/stack/sheet/animalhide/human = W_RARE,
/obj/item/stack/sheet/animalhide/lizard = W_RARE,
+ /obj/item/stack/sheet/animalhide/vox = W_RARE,
/obj/item/stack/sheet/animalhide/monkey = W_RARE,
/obj/item/stack/sheet/animalhide/xeno = W_RARE,
/obj/item/storage/bag/money = W_RARE,
@@ -581,6 +584,7 @@ GLOBAL_LIST_INIT(maintenance_loot_makeshift,list(
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/golem/adamantine = W_MYTHICAL,
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/gorilla = W_MYTHICAL,
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/lizard = W_MYTHICAL,
+ /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/vox = W_MYTHICAL,
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/moth = W_MYTHICAL,
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/plant = W_MYTHICAL,
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/shadow = W_MYTHICAL,
diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm
index 5f3f0c4c1f0f9..e668b1bcf95ee 100644
--- a/code/controllers/subsystem/traumas.dm
+++ b/code/controllers/subsystem/traumas.dm
@@ -139,7 +139,10 @@ SUBSYSTEM_DEF(traumas)
/obj/item/clothing/suit/chickensuit, /obj/item/clothing/head/chicken,
/obj/item/clothing/suit/toggle/owlwings, /obj/item/clothing/under/costume/owl, /obj/item/clothing/mask/gas/owl_mask,
/obj/item/clothing/under/costume/griffin, /obj/item/clothing/shoes/griffin, /obj/item/clothing/head/griffin,
- /obj/item/clothing/head/helmet/space/freedom, /obj/item/clothing/suit/space/freedom)),
+ /obj/item/clothing/head/helmet/space/freedom, /obj/item/clothing/suit/space/freedom, /obj/item/toy/plush/voxplushie,
+ /obj/item/clothing/mask/breath/vox, /obj/item/clothing/suit/space/vox, /obj/item/clothing/head/helmet/space/vox,
+ /obj/item/clothing/shoes/magboots/vox, /obj/item/clothing/gloves/color/yellow/vox, /obj/item/flag/species/vox, /obj/item/stack/sheet/animalhide/vox,
+ /obj/item/organ/tail/vox)),
"anime" = typecacheof(list(/obj/item/clothing/under/costume/schoolgirl, /obj/item/katana, /obj/item/reagent_containers/food/snacks/sashimi, /obj/item/reagent_containers/food/snacks/chawanmushi,
/obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja,
@@ -162,7 +165,8 @@ SUBSYSTEM_DEF(traumas)
"the supernatural" = typecacheof(list(/datum/species/golem/clockwork, /datum/species/golem/runic)),
"aliens" = typecacheof(list(/datum/species/abductor, /datum/species/jelly, /datum/species/pod,
/datum/species/shadow, /datum/species/polysmorph)),
- "anime" = typecacheof(list(/datum/species/human/felinid))
+ "anime" = typecacheof(list(/datum/species/human/felinid)),
+ "birds" = typecacheof(list(/datum/species/vox))
)
return SS_INIT_SUCCESS
diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm
index 77a842eed651a..261266e8cc48b 100644
--- a/code/datums/components/bloodysoles.dm
+++ b/code/datums/components/bloodysoles.dm
@@ -215,7 +215,7 @@ Like its parent but can be applied to carbon mobs instead of clothing items
*/
/datum/component/bloodysoles/feet
- var/static/mutable_appearance/bloody_feet
+ var/mutable_appearance/bloody_feet
/datum/component/bloodysoles/feet/Initialize()
if(!iscarbon(parent))
@@ -236,6 +236,11 @@ Like its parent but can be applied to carbon mobs instead of clothing items
/datum/component/bloodysoles/feet/update_icon()
. = list()
if(ishuman(wielder))// Monkeys get no bloody feet :(
+ var/obj/item/bodypart/l_leg/left_leg = wielder.get_bodypart(BODY_ZONE_L_LEG)
+ var/obj/item/bodypart/r_leg/right_leg = wielder.get_bodypart(BODY_ZONE_R_LEG)
+ if(left_leg?.species_id == right_leg?.species_id)
+ if(icon_exists(bloody_feet.icon, "shoeblood_[left_leg.species_id]"))
+ bloody_feet.icon_state = "shoeblood_[left_leg.species_id]"
if(HAS_BLOOD_DNA(wielder))
bloody_feet.color = get_blood_dna_color(wielder.return_blood_DNA())
. += bloody_feet
diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm
index 5b667d4a6d540..588181d5b52a3 100644
--- a/code/datums/diseases/advance/symptoms/shedding.dm
+++ b/code/datums/diseases/advance/symptoms/shedding.dm
@@ -44,14 +44,13 @@ BONUS
to_chat(H, span_warning("Your hair starts to fall out in clumps..."))
addtimer(CALLBACK(src, PROC_REF(Shed), H, FALSE), 50)
if(5)
- if(!(H.facial_hair_style == "Shaved") || !(H.hair_style == "Bald"))
+ if(!(H.facial_hair_style == "Shaved") || !(H.hair_style == "Bald") || !(H.hair_style == "None"))
to_chat(H, span_warning("Your hair starts to fall out in clumps..."))
addtimer(CALLBACK(src, PROC_REF(Shed), H, TRUE), 50)
/datum/symptom/shedding/proc/Shed(mob/living/carbon/human/H, fullbald)
if(fullbald)
- H.facial_hair_style = "Shaved"
- H.hair_style = "Bald"
+ H.dna.species.go_bald()
else
H.hair_style = "Balding Hair"
H.update_hair()
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 64884ff14bf3b..5759cb356131a 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -191,6 +191,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
if(features["mcolor"])
L[DNA_MUTANT_COLOR_BLOCK] = sanitize_hexcolor(features["mcolor"], include_crunch = FALSE)
+ if(features["mcolor_secondary"])
+ L[DNA_MUTANT_COLOR_SECONDARY] = sanitize_hexcolor(features["mcolor_secondary"], include_crunch = FALSE)
if(features["body_markings"])
L[DNA_LIZARD_MARKINGS_BLOCK] = construct_block(GLOB.body_markings_list.Find(features["body_markings"]), GLOB.body_markings_list.len)
if(features["tail_lizard"])
@@ -231,6 +233,16 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
L[DNA_POD_HAIR_BLOCK] = construct_block(GLOB.pod_hair_list.Find(features["pod_hair"]), GLOB.pod_hair_list.len)
if(features["pod_flower"])
L[DNA_POD_FLOWER_BLOCK] = construct_block(GLOB.pod_flower_list.Find(features["pod_flower"]), GLOB.pod_flower_list.len)
+ if(features["vox_quills"])
+ L[DNA_VOX_QUILLS_BLOCK] = construct_block(GLOB.vox_quills_list.Find(features["vox_quills"]), GLOB.vox_quills_list.len)
+ if(features["vox_facial_quills"])
+ L[DNA_VOX_FACIAL_QUILLS_BLOCK] = construct_block(GLOB.vox_facial_quills_list.Find(features["vox_facial_quills"]), GLOB.vox_facial_quills_list.len)
+ if(features["vox_tail_markings"])
+ L[DNA_VOX_TAIL_MARKINGS_BLOCK] = construct_block(GLOB.vox_tail_markings_list.Find(features["vox_tail_markings"]), GLOB.vox_tail_markings_list.len)
+ if(features["vox_body_markings"])
+ L[DNA_VOX_BODY_MARKINGS_BLOCK] = construct_block(GLOB.vox_body_markings_list.Find(features["vox_body_markings"]), GLOB.vox_body_markings_list.len)
+ if(features["vox_skin_tone"])
+ L[DNA_VOX_SKIN_TONE_BLOCK] = construct_block(GLOB.vox_skin_tones.Find(features["vox_skin_tone"]), GLOB.vox_skin_tones.len)
for(var/blocknum in 1 to DNA_FEATURE_BLOCKS)
. += L[blocknum] || random_string(GET_UI_BLOCK_LEN(blocknum), GLOB.hex_characters)
@@ -348,6 +360,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
switch(blocknumber)
if(DNA_MUTANT_COLOR_BLOCK)
set_uni_feature_block(blocknumber, sanitize_hexcolor(features["mcolor"], include_crunch = FALSE))
+ if(DNA_MUTANT_COLOR_SECONDARY)
+ set_uni_feature_block(blocknumber, sanitize_hexcolor(features["mcolor_secondary"], include_crunch = FALSE))
if(DNA_LIZARD_MARKINGS_BLOCK)
set_uni_feature_block(blocknumber, construct_block(GLOB.body_markings_list.Find(features["body_markings"]), GLOB.body_markings_list.len))
if(DNA_LIZARD_TAIL_BLOCK)
@@ -388,6 +402,16 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
set_uni_feature_block(blocknumber, construct_block(GLOB.pod_hair_list.Find(features["pod_hair"]), GLOB.pod_hair_list.len))
if(DNA_POD_FLOWER_BLOCK)
set_uni_feature_block(blocknumber, construct_block(GLOB.pod_flower_list.Find(features["pod_flower"]), GLOB.pod_flower_list.len))
+ if(DNA_VOX_QUILLS_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.vox_quills_list.Find(features["vox_quills"]), GLOB.vox_quills_list.len))
+ if(DNA_VOX_FACIAL_QUILLS_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.vox_facial_quills_list.Find(features["vox_facial_quills"]), GLOB.vox_facial_quills_list.len))
+ if(DNA_VOX_TAIL_MARKINGS_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.vox_tail_markings_list.Find(features["vox_tail_markings"]), GLOB.vox_tail_markings_list.len))
+ if(DNA_VOX_BODY_MARKINGS_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.vox_body_markings_list.Find(features["vox_body_markings"]), GLOB.vox_body_markings_list.len))
+ if(DNA_VOX_SKIN_TONE_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.vox_skin_tones.Find(features["vox_skin_tone"]), GLOB.vox_skin_tones.len))
//Please use add_mutation or activate_mutation instead
/datum/dna/proc/force_give(datum/mutation/human/HM)
@@ -596,12 +620,18 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
eye_color = sanitize_hexcolor(get_uni_identity_block(structure, DNA_EYE_COLOR_BLOCK))
facial_hair_style = GLOB.facial_hair_styles_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIR_STYLE_BLOCK), GLOB.facial_hair_styles_list.len)]
if(HAS_TRAIT(src, TRAIT_BALD))
- hair_style = "Bald"
+ if(isvox(src))
+ dna.features["vox_quills"] = "None"
+ dna.update_uf_block(DNA_VOX_QUILLS_BLOCK)
+ else
+ hair_style = "Bald"
else
hair_style = GLOB.hair_styles_list[deconstruct_block(get_uni_identity_block(structure, DNA_HAIR_STYLE_BLOCK), GLOB.hair_styles_list.len)]
var/features = dna.unique_features
if(dna.features["mcolor"])
dna.features["mcolor"] = sanitize_hexcolor(get_uni_feature_block(features, DNA_MUTANT_COLOR_BLOCK))
+ if(dna.features["mcolor_secondary"])
+ dna.features["mcolor_secondary"] = sanitize_hexcolor(get_uni_feature_block(features, DNA_MUTANT_COLOR_SECONDARY))
if(dna.features["body_markings"])
dna.features["body_markings"] = GLOB.body_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_MARKINGS_BLOCK), GLOB.body_markings_list.len)]
if(dna.features["tail_lizard"])
@@ -645,6 +675,16 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
dna.features["pod_hair"] = GLOB.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), GLOB.pod_hair_list.len)]
if(dna.features["pod_flower"])
dna.features["pod_flower"] = GLOB.pod_flower_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_FLOWER_BLOCK), GLOB.pod_flower_list.len)]
+ if(dna.features["vox_quills"])
+ dna.features["vox_quills"] = GLOB.vox_quills_list[deconstruct_block(get_uni_feature_block(features, DNA_VOX_QUILLS_BLOCK), GLOB.vox_quills_list.len)]
+ if(dna.features["vox_facial_quills"])
+ dna.features["vox_facial_quills"] = GLOB.vox_facial_quills_list[deconstruct_block(get_uni_feature_block(features, DNA_VOX_FACIAL_QUILLS_BLOCK), GLOB.vox_facial_quills_list.len)]
+ if(dna.features["vox_tail_markings"])
+ dna.features["vox_tail_markings"] = GLOB.vox_tail_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_VOX_TAIL_MARKINGS_BLOCK), GLOB.vox_tail_markings_list.len)]
+ if(dna.features["vox_body_markings"])
+ dna.features["vox_body_markings"] = GLOB.vox_body_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_VOX_BODY_MARKINGS_BLOCK), GLOB.vox_body_markings_list.len)]
+ if(dna.features["vox_skin_tone"])
+ dna.features["vox_skin_tone"] = GLOB.vox_skin_tones[deconstruct_block(get_uni_feature_block(features, DNA_VOX_SKIN_TONE_BLOCK), GLOB.vox_skin_tones.len)]
if(icon_update)
dna.species.handle_body(src) // We want 'update_body_parts()' to be called only if mutcolor_update is TRUE, so no 'update_body()' here.
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index 5ff180ddfe0f6..2ea07ea48d0e8 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -146,6 +146,8 @@
. = message_monkey
else if(isipc(user) && message_ipc)
. = message_ipc
+ else if(isvox(user) && message_vox)
+ . = message_vox
else if(isanimal(user) && message_simple)
. = message_simple
diff --git a/code/datums/greyscale/json_configs/jumpsuit_prison_worn.json b/code/datums/greyscale/json_configs/jumpsuit_prison_worn.json
index 0096c95e812a0..abe6be92981d7 100644
--- a/code/datums/greyscale/json_configs/jumpsuit_prison_worn.json
+++ b/code/datums/greyscale/json_configs/jumpsuit_prison_worn.json
@@ -12,6 +12,19 @@
"blend_mode": "overlay"
}
],
+ "jumpsuit_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_prison_vox",
+ "blend_mode": "overlay"
+ }
+ ],
"jumpsuit_d" : [
{
"type": "icon_state",
@@ -25,6 +38,19 @@
"blend_mode": "overlay"
}
],
+ "jumpsuit_d_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_d_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_d_prison_vox",
+ "blend_mode": "overlay"
+ }
+ ],
"jumpskirt": [
{
"type": "icon_state",
@@ -38,6 +64,19 @@
"blend_mode": "overlay"
}
],
+ "jumpskirt_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_prison_vox",
+ "blend_mode": "overlay"
+ }
+ ],
"jumpskirt_d" : [
{
"type": "icon_state",
@@ -50,5 +89,18 @@
"icon_state": "jumpskirt_d_prison",
"blend_mode": "overlay"
}
+ ],
+ "jumpskirt_d_vox" : [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_d_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_d_prison_vox",
+ "blend_mode": "overlay"
+ }
]
}
diff --git a/code/datums/greyscale/json_configs/jumpsuit_worn.json b/code/datums/greyscale/json_configs/jumpsuit_worn.json
index 99bc3fc19874d..04eb78e8dac14 100644
--- a/code/datums/greyscale/json_configs/jumpsuit_worn.json
+++ b/code/datums/greyscale/json_configs/jumpsuit_worn.json
@@ -12,6 +12,19 @@
"blend_mode": "overlay"
}
],
+ "jumpsuit_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_accessories_vox",
+ "blend_mode": "overlay"
+ }
+ ],
"jumpsuit_d" : [
{
"type": "icon_state",
@@ -20,6 +33,14 @@
"color_ids": [ 1 ]
}
],
+ "jumpsuit_d_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpsuit_d_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
"jumpsuit_l": [
{
"type": "icon_state",
@@ -54,6 +75,19 @@
"blend_mode": "overlay"
}
],
+ "jumpskirt_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_accessories_vox",
+ "blend_mode": "overlay"
+ }
+ ],
"jumpskirt_d" : [
{
"type": "icon_state",
@@ -61,5 +95,13 @@
"blend_mode": "overlay",
"color_ids": [ 1 ]
}
+ ],
+ "jumpskirt_d_vox" : [
+ {
+ "type": "icon_state",
+ "icon_state": "jumpskirt_d_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
]
}
diff --git a/code/datums/greyscale/json_configs/sneakers_orange_worn.json b/code/datums/greyscale/json_configs/sneakers_orange_worn.json
index fb0a07ff1969f..d28b36812f6ef 100644
--- a/code/datums/greyscale/json_configs/sneakers_orange_worn.json
+++ b/code/datums/greyscale/json_configs/sneakers_orange_worn.json
@@ -13,6 +13,20 @@
"color_ids": [ 2 ]
}
],
+ "sneakers_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_back_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_front_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 2 ]
+ }
+ ],
"sneakers_chained": [
{
"type": "icon_state",
@@ -31,5 +45,24 @@
"icon_state": "sneakers_chained",
"blend_mode": "overlay"
}
+ ],
+ "sneakers_chained_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_back_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_front_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 2 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_chained_vox",
+ "blend_mode": "overlay"
+ }
]
}
diff --git a/code/datums/greyscale/json_configs/sneakers_worn.json b/code/datums/greyscale/json_configs/sneakers_worn.json
index 50a064bb02b66..4d808820ec05d 100644
--- a/code/datums/greyscale/json_configs/sneakers_worn.json
+++ b/code/datums/greyscale/json_configs/sneakers_worn.json
@@ -12,5 +12,19 @@
"blend_mode": "overlay",
"color_ids": [ 2 ]
}
+ ],
+ "sneakers_vox": [
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_back_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "sneakers_front_vox",
+ "blend_mode": "overlay",
+ "color_ids": [ 2 ]
+ }
]
}
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 6f1bc7dcac91f..ce82580ff2a6d 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -356,10 +356,12 @@
/datum/quirk/telomeres_long/check_quirk(datum/preferences/prefs)
var/datum/species/species_type = prefs.read_preference(/datum/preference/choiced/species)
- var/disallowed_trait = (NO_DNA_COPY in initial(species_type.species_traits)) //Can't pick if you have no DNA bruv.
-
- if(disallowed_trait)
+ var/no_dna = (NO_DNA_COPY in initial(species_type.species_traits)) //Can't pick if you have no DNA bruv.
+ var/no_clone = (TRAIT_NOCLONE in initial(species_type.inherent_traits))
+ if(no_dna)
return "You have no DNA!"
+ else if(no_clone)
+ return "Your species cannot be cloned!"
return FALSE
/datum/quirk/marine
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 5c9498f595fdc..2e025359558e3 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -828,10 +828,12 @@
/datum/quirk/telomeres_short/check_quirk(datum/preferences/prefs)
var/datum/species/species_type = prefs.read_preference(/datum/preference/choiced/species)
- var/disallowed_trait = (NO_DNA_COPY in initial(species_type.species_traits)) //Can't pick if you have no DNA bruv.
-
- if(disallowed_trait)
+ var/no_dna = (NO_DNA_COPY in initial(species_type.species_traits)) //Can't pick if you have no DNA bruv.
+ var/no_clone = (TRAIT_NOCLONE in initial(species_type.inherent_traits))
+ if(no_dna)
return "You have no DNA!"
+ else if(no_clone)
+ return "Your species cannot be cloned!"
return FALSE
/datum/quirk/body_purist
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index bcfea380f6014..8cbb39d5c893b 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -174,7 +174,7 @@
/datum/quirk/colorist/check_quirk(datum/preferences/prefs)
var/datum/species/species_type = prefs.read_preference(/datum/preference/choiced/species)
- var/disallowed_trait = (HAIR in initial(species_type.species_traits)) // No Hair
+ var/disallowed_trait = (HAIR in initial(species_type.species_traits)) || ("vox_quills" in initial(species_type.mutant_bodyparts)) // No Hair
if(!disallowed_trait)
return "You don't have hair!"
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index bdfdd5ff63a93..ba228387fefb7 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -219,6 +219,7 @@ GLOBAL_VAR(changeling_team_objective_type)
new_flesh_item.item_state = chosen_prof.inhand_icon_state_list[slot]
new_flesh_item.worn_icon = chosen_prof.worn_icon_list[slot]
new_flesh_item.worn_icon_state = chosen_prof.worn_icon_state_list[slot]
+ new_flesh_item.sprite_sheets = chosen_prof.sprite_sheets_list[slot]
if(equip)
user.equip_to_slot_or_del(new_flesh_item, GLOB.slot2slot[slot])
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 6086a909e646e..321b34a75505c 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -53,7 +53,8 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/item/storage/box/heretic_box = 1,
/obj/item/toy/plush/mothplushie = 2,
/obj/item/toy/plush/abductor = 2,
- /obj/item/toy/plush/abductor/agent = 1))
+ /obj/item/toy/plush/abductor/agent = 1,
+ /obj/item/toy/plush/voxplushie = 2))
/obj/machinery/computer/arcade
name = "random arcade"
diff --git a/code/game/machinery/limbgrower.dm b/code/game/machinery/limbgrower.dm
index 7fac837c3ec37..c49b87da94c4e 100644
--- a/code/game/machinery/limbgrower.dm
+++ b/code/game/machinery/limbgrower.dm
@@ -24,7 +24,7 @@
/// Our internal techweb for limbgrower designs.
var/datum/techweb/stored_research
/// All the categories of organs we can print.
- var/list/categories = list("human", "lizard", "moth", "plasmaman", "ethereal", "polysmorph", "other")
+ var/list/categories = list("human", "lizard", "moth", "plasmaman", "ethereal", "polysmorph", "vox", "other")
//yogs grower a little different because we're going to allow meats to be converted to synthflesh because hugbox
var/list/accepted_biomass = list(
/obj/item/reagent_containers/food/snacks/meat/slab/monkey = 25,
diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm
index 39962577f6f6d..4dc99243424b4 100644
--- a/code/game/objects/effects/spawners/lootdrop.dm
+++ b/code/game/objects/effects/spawners/lootdrop.dm
@@ -84,12 +84,15 @@
///obj/item/organ/tongue/ethereal,
/obj/item/organ/tongue/robot,
/obj/item/organ/tongue/zombie,
+ /obj/item/organ/tongue/vox,
/obj/item/organ/appendix,
/obj/item/organ/liver/fly,
/obj/item/organ/lungs/plasmaman,
/obj/item/organ/lungs/ethereal,
+ /obj/item/organ/lungs/vox,
/obj/item/organ/tail/cat,
/obj/item/organ/tail/lizard,
+ /obj/item/organ/tail/vox
)
/obj/effect/spawner/lootdrop/plushies
@@ -115,7 +118,8 @@
/obj/item/toy/plush/inorixplushie,
/obj/item/toy/plush/flowerbunch,
/obj/item/toy/plush/goatplushie,
- /obj/item/toy/plush/realgoat
+ /obj/item/toy/plush/realgoat,
+ /obj/item/toy/plush/voxplushie
)
/obj/effect/spawner/lootdrop/techshell
diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm
index 60e4073b12dca..120a3366f1701 100644
--- a/code/game/objects/items/cosmetics.dm
+++ b/code/game/objects/items/cosmetics.dm
@@ -118,10 +118,18 @@
return BRUTELOSS
/obj/item/razor/proc/shave(mob/living/carbon/human/H, location = BODY_ZONE_PRECISE_MOUTH)
- if(location == BODY_ZONE_PRECISE_MOUTH)
- H.facial_hair_style = "Shaved"
+ if(isvox(H))//both hair and bodyparts need refactors to do this cleanly
+ if(location == BODY_ZONE_PRECISE_MOUTH)
+ H.dna.features["vox_facial_quills"] = "None"
+ H.dna.update_uf_block(DNA_VOX_FACIAL_QUILLS_BLOCK)
+ else
+ H.dna.features["vox_quills"] = "None"
+ H.dna.update_uf_block(DNA_VOX_QUILLS_BLOCK)
else
- H.hair_style = "Skinhead"
+ if(location == BODY_ZONE_PRECISE_MOUTH)
+ H.facial_hair_style = "Shaved"
+ else
+ H.hair_style = "Skinhead"
H.update_hair()
playsound(loc, 'sound/items/welder2.ogg', 20, 1)
@@ -134,85 +142,117 @@
if((location in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_HEAD)) && !H.get_bodypart(BODY_ZONE_HEAD))
to_chat(user, span_warning("[H] doesn't have a head!"))
return
+ var/hair_name = "hair"
+ if(isvox(H))
+ hair_name = "quills"
if(location == BODY_ZONE_PRECISE_MOUTH)
if(!user.combat_mode)
if(H.gender == MALE)
if (H == user)
- to_chat(user, span_warning("You need a mirror to properly style your own facial hair!"))
+ to_chat(user, span_warning("You need a mirror to properly style your own facial [hair_name]!"))
return
if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
- var/new_style = input(user, "Select a facial hair style", "Grooming") as null|anything in GLOB.facial_hair_styles_list
+ var/list/hair_list = GLOB.facial_hair_styles_list
+ if(isvox(H))
+ hair_list = GLOB.vox_facial_quills_list
+ var/new_style = input(user, "Select a facial [hair_name] style", "Grooming") as null|anything in hair_list
if(!get_location_accessible(H, location))
to_chat(user, span_warning("The mask is in the way!"))
return
- user.visible_message(span_notice("[user] tries to change [H]'s facial hair style using [src]."), span_notice("You try to change [H]'s facial hair style using [src]."))
+ user.visible_message(span_notice("[user] tries to change [H]'s facial [hair_name] style using [src]."), span_notice("You try to change [H]'s facial [hair_name] style using [src]."))
if(new_style && do_after(user, 6 SECONDS, H))
- user.visible_message(span_notice("[user] successfully changes [H]'s facial hair style using [src]."), span_notice("You successfully change [H]'s facial hair style using [src]."))
- H.facial_hair_style = new_style
+ user.visible_message(span_notice("[user] successfully changes [H]'s facial [hair_name] style using [src]."), span_notice("You successfully change [H]'s facial [hair_name] style using [src]."))
+ if(isvox(H))
+ H.dna.features["vox_facial_quills"] = new_style
+ H.dna.update_uf_block(DNA_VOX_FACIAL_QUILLS_BLOCK)
+ else
+ H.facial_hair_style = new_style
H.update_hair()
return
else
return
else
- if(!(FACEHAIR in H.dna.species.species_traits))
- to_chat(user, span_warning("There is no facial hair to shave!"))
- return
if(!get_location_accessible(H, location))
to_chat(user, span_warning("The mask is in the way!"))
return
- if(H.facial_hair_style == "Shaved")
- to_chat(user, span_warning("Already clean-shaven!"))
- return
-
+ if(!isvox(H))
+ if(!(FACEHAIR in H.dna.species.species_traits))
+ to_chat(user, span_warning("There is no facial hair to shave!"))
+ return
+ if(H.facial_hair_style == "Shaved")
+ to_chat(user, span_warning("Already clean-shaven!"))
+ return
+ else
+ if(!("vox_facial_quills" in H.dna.species.mutant_bodyparts))
+ to_chat(user, span_warning("There are no facial quills to shave!"))
+ return
+ if(H.dna.species.mutant_bodyparts["vox_facial_quills"] == "None")
+ to_chat(user, span_warning("Already clean-shaven!"))
+ return
if(H == user) //shaving yourself
- user.visible_message("[user] starts to shave [user.p_their()] facial hair with [src].", \
- span_notice("You take a moment to shave your facial hair with [src]..."))
+ user.visible_message("[user] starts to shave [user.p_their()] facial [hair_name] with [src].", \
+ span_notice("You take a moment to shave your facial [hair_name] with [src]..."))
if(do_after(user, 5 SECONDS, H))
- user.visible_message("[user] shaves [user.p_their()] facial hair clean with [src].", \
+ user.visible_message("[user] shaves [user.p_their()] facial [hair_name] clean with [src].", \
span_notice("You finish shaving with [src]. Fast and clean!"))
shave(H, location)
else
- user.visible_message(span_warning("[user] tries to shave [H]'s facial hair with [src]."), \
- span_notice("You start shaving [H]'s facial hair..."))
+ user.visible_message(span_warning("[user] tries to shave [H]'s facial [hair_name] with [src]."), \
+ span_notice("You start shaving [H]'s facial [hair_name]..."))
if(do_after(user, 5 SECONDS, target = H))
- user.visible_message(span_warning("[user] shaves off [H]'s facial hair with [src]."), \
- span_notice("You shave [H]'s facial hair clean off."))
+ user.visible_message(span_warning("[user] shaves off [H]'s facial [hair_name] with [src]."), \
+ span_notice("You shave [H]'s facial [hair_name] clean off."))
shave(H, location)
else if(location == BODY_ZONE_HEAD)
if(!user.combat_mode)
if (H == user)
- to_chat(user, span_warning("You need a mirror to properly style your own hair!"))
+ to_chat(user, span_warning("You need a mirror to properly style your own [hair_name]!"))
return
if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
- var/new_style = input(user, "Select a hair style", "Grooming") as null|anything in GLOB.hair_styles_list
+ var/hair_list = GLOB.hair_styles_list
+ if(isvox(H))
+ hair_list = GLOB.vox_quills_list
+ var/new_style = input(user, "Select a [hair_name] style", "Grooming") as null|anything in hair_list
if(!get_location_accessible(H, location))
to_chat(user, span_warning("The headgear is in the way!"))
return
if(HAS_TRAIT(H, TRAIT_BALD))
to_chat(user, span_warning("[H] is just way too bald. Like, really really bald."))
return
- user.visible_message(span_notice("[user] tries to change [H]'s hairstyle using [src]."), span_notice("You try to change [H]'s hairstyle using [src]."))
+ user.visible_message(span_notice("[user] tries to change [H]'s [hair_name]style using [src]."), span_notice("You try to change [H]'s [hair_name]style using [src]."))
if(new_style && do_after(user, 6 SECONDS, H))
- user.visible_message(span_notice("[user] successfully changes [H]'s hairstyle using [src]."), span_notice("You successfully change [H]'s hairstyle using [src]."))
- H.hair_style = new_style
+ user.visible_message(span_notice("[user] successfully changes [H]'s [hair_name]style using [src]."), span_notice("You successfully change [H]'s [hair_name]style using [src]."))
+ if(isvox(H))
+ H.dna.features["vox_quills"] = new_style
+ H.dna.update_uf_block(DNA_VOX_QUILLS_BLOCK)
+ else
+ H.hair_style = new_style
H.update_hair()
return
else
- if(!(HAIR in H.dna.species.species_traits))
- to_chat(user, span_warning("There is no hair to shave!"))
- return
if(!get_location_accessible(H, location))
to_chat(user, span_warning("The headgear is in the way!"))
return
- if(H.hair_style == "Bald" || H.hair_style == "Balding Hair" || H.hair_style == "Skinhead")
- to_chat(user, span_warning("There is not enough hair left to shave!"))
- return
-
+ if(!isvox(H))
+ if(!(HAIR in H.dna.species.species_traits))
+ to_chat(user, span_warning("There is no hair to shave!"))
+ return
+
+ if(H.hair_style == "Bald" || H.hair_style == "Balding Hair" || H.hair_style == "Skinhead")
+ to_chat(user, span_warning("There is not enough hair left to shave!"))
+ return
+ else
+ if(!("vox_quills" in H.dna.species.mutant_bodyparts))
+ to_chat(user, span_warning("There are no quills to shave!"))
+ return
+ if(H.dna.species.mutant_bodyparts["vox_quills"] == "None")
+ to_chat(user, span_warning("There are not enough quills left to shave!"))
+ return
if(H == user) //shaving yourself
user.visible_message("[user] starts to shave [user.p_their()] head with [src].", \
span_notice("You start to shave your head with [src]..."))
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index ea46fd2a4d7c7..2785da73c02da 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -35,7 +35,7 @@
var/drawtype
var/text_buffer = ""
- var/static/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","star","poseur tag","prolizard","antilizard")
+ var/static/list/graffiti = list("amyjon","face","matt","revolution","engie","guy","end","dwarf","uboa","body","cyka","star","poseur tag","prolizard","antilizard","voxpox")
var/static/list/symbols = list("danger","firedanger","electricdanger","biohazard","radiation","safe","evac","space","med","trade","shop","food","peace","like","skull","nay","heart","credit")
var/static/list/drawings = list("smallbrush","brush","largebrush","splatter","snake","stickman","carp","ghost","clown","taser","disk","fireaxe","toolbox","corgi","cat","toilet","blueprint","beepsky","scroll","bottle","shotgun")
var/static/list/oriented = list("arrow","line","thinline","shortline","body","chevron","footprint","clawprint","pawprint") // These turn to face the same way as the drawer
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index ce024afeadc7a..c61caed09844f 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -264,6 +264,7 @@
languages = list(
/datum/language/bonespeak,
/datum/language/draconic,
+ /datum/language/vox,
/datum/language/english,
/datum/language/etherean,
/datum/language/felinid,
@@ -280,6 +281,7 @@
languages = list(
/datum/language/bonespeak,
/datum/language/draconic,
+ /datum/language/vox,
/datum/language/english,
/datum/language/etherean,
/datum/language/felinid,
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 2458430625dad..364f4a9640bff 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -146,13 +146,16 @@
new /obj/item/radio/off(src)
/obj/item/storage/box/survival/proc/wardrobe_removal()
- if(!isplasmaman(loc)) //We need to specially fill the box with plasmaman gear, since it's intended for one
+ if(!ishuman(loc))
return
- var/obj/item/mask = locate(/obj/item/clothing/mask/breath) in src
- var/obj/item/internals = locate(/obj/item/tank/internals/emergency_oxygen) in src
- new /obj/item/tank/internals/plasmaman/belt(src)
- qdel(mask) // Get rid of the items that shouldn't be
- qdel(internals)
+ var/mob/living/carbon/human/box_owner = loc
+ if(!length(box_owner.dna?.species?.survival_box_replacements))
+ return
+ var/list/survival_box_replacements_delete = box_owner.dna.species.survival_box_replacements["items_to_delete"]
+ var/list/survival_box_replacements_add = box_owner.dna.species.survival_box_replacements["new_items"]
+ var/list/items_to_delete = survival_box_replacements_delete?.Copy() || list()
+ var/list/new_items = survival_box_replacements_add?.Copy() || list()
+ box_owner.dna.species.survival_box_replacement(box_owner, src, items_to_delete, new_items)
/obj/item/storage/box/survival/mining
mask_type = /obj/item/clothing/mask/gas/explorer
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index e16f595fed15a..fe85b6ecc311c 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -134,7 +134,7 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
if(!(resistance_flags & ON_FIRE) && (resistance_flags & FLAMMABLE) && !(resistance_flags & FIRE_PROOF))
resistance_flags |= ON_FIRE
SSfire_burning.processing[src] = src
- add_overlay(GLOB.fire_overlay, TRUE)
+ add_overlay(custom_fire_overlay ? custom_fire_overlay : GLOB.fire_overlay)
return 1
///called when the obj is destroyed by fire
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 1043e7bafec1e..f3707d937dd7d 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -156,6 +156,7 @@
new /obj/item/reagent_containers/blood/OMinus(src)
new /obj/item/reagent_containers/blood/OPlus(src)
new /obj/item/reagent_containers/blood/lizard(src)
+ new /obj/item/reagent_containers/blood/vox(src)
new /obj/item/reagent_containers/blood/gorilla(src) // yogs -- gorilla people
new /obj/item/reagent_containers/blood/ethereal(src)
for(var/i in 1 to 3)
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index c056418554c9e..8c032d362b999 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -28,11 +28,16 @@
/obj/structure/mirror/proc/get_choices(mob/living/carbon/human/H)
. = list()
var/datum/species/S = H.dna.species
- if((FACEHAIR in S.species_traits))
- . += list(FACIAL_HAIR = list("select a facial hair style", GLOB.facial_hair_styles_list))
+ var/list/facial_hair_list = GLOB.facial_hair_styles_list
+ var/list/hair_list = GLOB.hair_styles_list
+ if(isvox(H))
+ facial_hair_list = GLOB.vox_facial_quills_list
+ hair_list = GLOB.vox_quills_list
+ if((FACEHAIR in S.species_traits) || (FACEHAIRCOLOR in S.species_traits))
+ . += list(FACIAL_HAIR = list("select a facial hair style", facial_hair_list))
. += list(FACE_HAIR_COLOR)
- if((HAIR in S.species_traits))
- . += list(HEAD_HAIR = list("select a hair style", GLOB.hair_styles_list))
+ if((HAIR in S.species_traits) || (HAIRCOLOR in S.species_traits))
+ . += list(HEAD_HAIR = list("select a hair style", hair_list))
. += list(HAIR_COLOR)
// for things that dont use a list style syntax
@@ -60,14 +65,22 @@
return
switch(selectiontype)
if(FACIAL_HAIR)
- H.facial_hair_style = selection
+ if(isvox(H))
+ H.dna.features["vox_facial_quills"] = selection
+ H.dna.update_uf_block(DNA_VOX_FACIAL_QUILLS_BLOCK)
+ else
+ H.facial_hair_style = selection
H.update_hair()
return TRUE
if(HEAD_HAIR)
- if(HAS_TRAIT(H, TRAIT_BALD) && selection != "Bald")
+ if(HAS_TRAIT(H, TRAIT_BALD) && !((selection == "Bald") || (selection == ("None"))))
to_chat(H, span_notice("If only growing back hair were that easy for you..."))
return TRUE
- H.hair_style = selection
+ if(isvox(H))
+ H.dna.features["vox_quills"] = selection
+ H.dna.update_uf_block(DNA_VOX_QUILLS_BLOCK)
+ else
+ H.hair_style = selection
H.update_hair()
return TRUE
@@ -190,6 +203,8 @@
. += list(SKIN_COLOR = list("select a new skintone", GLOB.skin_tones))
if((MUTCOLORS in S.species_traits) && !(NOCOLORCHANGE in S.species_traits))
. += list(MUTANT_COLOR)
+ if((MUTCOLORS_SECONDARY in S.species_traits))
+ . += list("Secondary mutant color")
. += list(NAME)
/obj/structure/mirror/magic/preapply_choices(selectiontype, mob/living/carbon/human/H)
@@ -228,6 +243,16 @@
H.update_hair()
H.update_body_parts()
H.update_mutations_overlay() // no hulk lizard
+ if("Secondary mutant color")
+ var/new_mutantcolor = input(H, "Choose your secondary mutant color:", "Race change",H.dna.features["mcolor_secondary"]) as color|null
+ if(!new_mutantcolor)
+ return TRUE
+ H.dna.features["mcolor_secondary"] = sanitize_hexcolor(new_mutantcolor)
+ H.dna.update_uf_block(DNA_MUTANT_COLOR_SECONDARY)
+ H.update_body()
+ H.update_hair()
+ H.update_body_parts()
+ H.update_mutations_overlay()
return TRUE
diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm
index 49c1f9927fdf1..700d5182b9dc3 100644
--- a/code/modules/admin/create_mob.dm
+++ b/code/modules/admin/create_mob.dm
@@ -25,6 +25,7 @@
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
human.dna.features["mcolor"] = "#[random_color()]"
+ human.dna.features["mcolor_secondary"] = "#[random_color()]"
human.dna.features["pretcolor"] = GLOB.color_list_preternis[pick(GLOB.color_list_preternis)]
human.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard)
human.dna.features["tail_polysmorph"] = pick(GLOB.tails_list_polysmorph)
@@ -44,6 +45,11 @@
human.dna.features["preternis_core"] = pick(GLOB.preternis_core_list)
human.dna.features["pod_hair"] = pick(GLOB.pod_hair_list)
human.dna.features["pod_flower"] = GLOB.pod_flower_list[human.dna.features["pod_hair"]]
+ human.dna.features["vox_skin_tone"] = pick(GLOB.vox_skin_tones)
+ human.dna.features["vox_quills"] = pick(GLOB.vox_quills_list)
+ human.dna.features["vox_facial_quills"] = pick(GLOB.vox_facial_quills_list)
+ human.dna.features["vox_body_markings"] = pick(GLOB.vox_body_markings_list)
+ human.dna.features["vox_tail_markings"] = pick(GLOB.vox_tail_markings_list)
human.update_body()
human.update_hair()
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index eb305df0c8ddf..6834a5b0ab36d 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -345,6 +345,7 @@
prof.righthand_file_list[slot] = I.righthand_file
prof.worn_icon_list[slot] = I.worn_icon
prof.worn_icon_state_list[slot] = I.worn_icon_state
+ prof.sprite_sheets_list[slot] = I.sprite_sheets
prof.exists_list[slot] = 1
else
continue
@@ -676,6 +677,7 @@
new_profile.socks = socks
new_profile.worn_icon_list = worn_icon_list.Copy()
new_profile.worn_icon_state_list = worn_icon_state_list.Copy()
+ new_profile.sprite_sheets_list = sprite_sheets_list.Copy()
new_profile.stored_scars = stored_scars.Copy()
/datum/antagonist/changeling/xenobio
diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm
index 09fe1626b5a95..b31ac8c0e8ce6 100644
--- a/code/modules/antagonists/highlander/highlander.dm
+++ b/code/modules/antagonists/highlander/highlander.dm
@@ -48,6 +48,10 @@
if(!isplasmaman(H)) //no killing plasmies
H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/kilt/highlander(H), ITEM_SLOT_ICLOTHING)
H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), ITEM_SLOT_HEAD)
+ if(isvox(H))
+ H.equip_to_slot_or_del(new /obj/item/tank/internals/emergency_oxygen/vox(H), ITEM_SLOT_BELT)
+ H.equip_to_slot_or_del(new /obj/item/clothing/mask/breath(H), ITEM_SLOT_MASK)
+ H.open_internals(H.get_item_by_slot(ITEM_SLOT_BELT))
else
H.equip_to_slot_or_del(new /obj/item/clothing/under/plasmaman(H), ITEM_SLOT_ICLOTHING)
H.equip_to_slot_or_del(new /obj/item/tank/internals/plasmaman/belt/full(H), ITEM_SLOT_BELT)
diff --git a/code/modules/atmospherics/auxgm/gas_types.dm b/code/modules/atmospherics/auxgm/gas_types.dm
index 0c559d9d8e3aa..616d26f9978ad 100644
--- a/code/modules/atmospherics/auxgm/gas_types.dm
+++ b/code/modules/atmospherics/auxgm/gas_types.dm
@@ -3,6 +3,16 @@
specific_heat = 20
name = "Oxygen"
label = "O₂"
+ breath_alert_info = list(
+ not_enough_alert = list(
+ alert_category = "not_enough_oxy",
+ alert_type = /atom/movable/screen/alert/not_enough_oxy
+ ),
+ too_much_alert = list(
+ alert_category = "too_much_oxy",
+ alert_type = /atom/movable/screen/alert/too_much_oxy
+ )
+ )
ui_color = "blue"
/datum/gas/nitrogen
@@ -47,6 +57,16 @@
gas_overlay = "plasma"
moles_visible = MOLES_GAS_VISIBLE
flags = GAS_FLAG_DANGEROUS
+ breath_alert_info = list(
+ not_enough_alert = list(
+ alert_category = "not_enough_tox",
+ alert_type = /atom/movable/screen/alert/not_enough_tox,
+ ),
+ too_much_alert = list(
+ alert_category = "too_much_tox",
+ alert_type = /atom/movable/screen/alert/too_much_tox,
+ )
+ )
ui_color = "orange"
/datum/gas/water_vapor
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index 2c5d754fd67ba..be718754a097f 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -1428,7 +1428,7 @@
/datum/supply_pack/medical/bloodpacks
name = "Blood Pack Variety Crate"
- desc = "Contains eight different blood packs for reintroducing blood to patients."
+ desc = "Contains many different blood packs for reintroducing blood to patients."
cost = 3500
contains = list(/obj/item/reagent_containers/blood,
/obj/item/reagent_containers/blood,
@@ -1439,6 +1439,7 @@
/obj/item/reagent_containers/blood/OPlus,
/obj/item/reagent_containers/blood/OMinus,
/obj/item/reagent_containers/blood/lizard,
+ /obj/item/reagent_containers/blood/vox,
/obj/item/reagent_containers/blood/ethereal)
crate_name = "blood freezer"
crate_type = /obj/structure/closet/crate/freezer
diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm
index f2ed776c0e41e..884667cb24b5e 100644
--- a/code/modules/client/preferences/_preference.dm
+++ b/code/modules/client/preferences/_preference.dm
@@ -317,12 +317,12 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key())
/datum/preference/proc/is_accessible(datum/preferences/preferences)
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
-
- if (!isnull(relevant_mutant_bodypart) || !isnull(relevant_species_trait))
- var/species_type = preferences.read_preference(/datum/preference/choiced/species)
-
- var/datum/species/species = new species_type
- if (!(savefile_key in species.get_features()))
+ var/species_type = preferences.read_preference(/datum/preference/choiced/species)
+ var/datum/species/species = new species_type
+ if(species_type in blacklisted_species)
+ return FALSE
+ if(!isnull(relevant_mutant_bodypart) || !isnull(relevant_species_trait))
+ if(!(savefile_key in species.get_features()))
return FALSE
if (!should_show_on_page(preferences.current_window))
diff --git a/code/modules/client/preferences/clothing.dm b/code/modules/client/preferences/clothing.dm
index 44e72c844861e..1b110178739ea 100644
--- a/code/modules/client/preferences/clothing.dm
+++ b/code/modules/client/preferences/clothing.dm
@@ -1,16 +1,22 @@
-/proc/generate_values_for_underwear(icon_file, list/accessory_list, list/icons)
+/proc/generate_values_for_underwear(icon_file, list/accessory_list, list/icons, bodyparts_file = 'icons/mob/human_parts_greyscale.dmi')
var/icon/lower_half = icon('icons/blanks/32x32.dmi', "nothing")
for (var/icon in icons)
- lower_half.Blend(icon('icons/mob/human_parts_greyscale.dmi', icon), ICON_OVERLAY)
+ lower_half.Blend(icon(bodyparts_file, icon), ICON_OVERLAY)
var/list/values = list()
-
- for (var/accessory_name in accessory_list)
+ var/list/undergarment_list = accessory_list.Copy()
+ for(var/undergarment_accessory in undergarment_list)
+ if(undergarment_accessory == "Nude")
+ continue
+ var/datum/sprite_accessory/undergarment = undergarment_list[undergarment_accessory]
+ if(!icon_exists(icon_file, undergarment.icon_state))
+ undergarment_list -= undergarment_accessory
+ for (var/accessory_name in undergarment_list)
var/icon/icon_with_socks = new(lower_half)
if (accessory_name != "Nude")
- var/datum/sprite_accessory/accessory = accessory_list[accessory_name]
+ var/datum/sprite_accessory/accessory = undergarment_list[accessory_name]
var/icon/accessory_icon = icon(icon_file, accessory.icon_state)
icon_with_socks.Blend(accessory_icon, ICON_OVERLAY)
diff --git a/code/modules/client/preferences/middleware/species.dm b/code/modules/client/preferences/middleware/species.dm
index 05fffbb328119..ed33227c8b033 100644
--- a/code/modules/client/preferences/middleware/species.dm
+++ b/code/modules/client/preferences/middleware/species.dm
@@ -22,9 +22,10 @@
dummy.equipOutfit(/datum/outfit/job/assistant/consistent, visualsOnly = TRUE)
dummy.dna.species.prepare_human_for_preview(dummy)
var/icon/dummy_icon = getFlatIcon(dummy)
- if(ismoth(dummy))
+ var/list/lazy_icons_species = list("moth", SPECIES_VOX)//no idea why it fails to render properly the normal way
+ if(dummy.dna.species.id in lazy_icons_species)
dummy_icon = null
- dummy_icon = icon('icons/mob/human.dmi', "moth")
+ dummy_icon = icon('icons/mob/human.dmi', "[dummy.dna.species.id]")
dummy_icon.Scale(64, 64)
dummy_icon.Crop(15, 64, 15 + 31, 64 - 31)
dummy_icon.Scale(64, 64)
diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm
index 1378543606a85..31e78911e304b 100644
--- a/code/modules/client/verbs/suicide.dm
+++ b/code/modules/client/verbs/suicide.dm
@@ -1,18 +1,18 @@
-#define SUICIDE_MESSAGE(p_their, p_theyre, p_them) pick( \
- "[src] is attempting to push [p_their] own head off [p_their] shoulders! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is pushing [p_their] thumbs into [p_their] eye sockets! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is ripping [p_their] own arms off! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is attempting to pull [p_their] own head off! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is aggressively grabbing [p_their] own neck! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is pulling [p_their] eyes out of their sockets! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is hugging [p_them]self to death! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is high-fiving [p_them]self to death! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is getting too high on life! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is attempting to bite [p_their] tongue off! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is jamming [p_their] thumbs into [p_their] eye sockets! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is twisting [p_their] own neck! It looks like [p_theyre] trying to commit suicide.", \
- "[src] is holding [p_their] breath! It looks like [p_theyre] trying to commit suicide.", \
-)
+GLOBAL_LIST_INIT(human_suicide_messages, list( \
+ "%%SUICIDER%% is attempting to push %%P_THEIR%% own head off %%P_THEIR%% shoulders! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is pushing %%P_THEIR%% thumbs into %%P_THEIR%% eye sockets! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is ripping %%P_THEIR%% own arms off! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is attempting to pull %%P_THEIR%% own head off! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is aggressively grabbing %%P_THEIR%% own neck! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is pulling %%P_THEIR%% eyes out of their sockets! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is hugging %%P_THEM%%self to death! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is high-fiving %%P_THEM%%self to death! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is getting too high on life! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is attempting to bite %%P_THEIR%% tongue off! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is jamming %%P_THEIR%% thumbs into %%P_THEIR%% eye sockets! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is twisting %%P_THEIR%% own neck! It looks like %%P_THEYRE%% trying to commit suicide.", \
+ "%%SUICIDER%% is holding %%P_THEIR%% breath! It looks like %%P_THEYRE%% trying to commit suicide.", \
+))
/mob/var/suiciding = 0
@@ -101,9 +101,13 @@
return
- var/suicide_message = SUICIDE_MESSAGE(p_their(), p_theyre(), p_them())
-
- visible_message(span_danger("[suicide_message]"), span_userdanger("[suicide_message]"))
+ var/list/suicide_messages = GLOB.human_suicide_messages.Copy()
+ suicide_messages |= dna.species.suicide_messages
+ var/chosen_message = pick(suicide_messages)
+ var/list/text_replacements = list("%%SUICIDER%%" = "[src]", "%%P_THEIR%%" = "[p_their()]", "%%P_THEM%%" = "[p_them()]", "%%P_THEYRE%%" = "[p_theyre()]")
+ for(var/find_text in text_replacements)
+ chosen_message = replacetext(chosen_message, find_text, text_replacements[find_text])
+ visible_message(span_danger(chosen_message), span_userdanger(chosen_message))
suicide_log()
@@ -278,5 +282,3 @@
to_chat(src, "Something inside your head stops your action!")
return
return TRUE
-
-#undef SUICIDE_MESSAGE
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index 28563976c4674..d3eafcd6e8a9f 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -252,6 +252,7 @@
item_target.worn_icon_state = initial(picked_item.worn_icon_state)
item_target.item_state = initial(picked_item.item_state)
+ item_target.sprite_sheets = initial(picked_item.sprite_sheets)
if(isclothing(item_target) && ispath(picked_item, /obj/item/clothing))
var/obj/item/clothing/CL = item_target
var/obj/item/clothing/PCL = picked_item
diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm
index a382e315bca89..d48ba7b96b3a9 100644
--- a/code/modules/clothing/gloves/_gloves.dm
+++ b/code/modules/clothing/gloves/_gloves.dm
@@ -33,6 +33,8 @@
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves")
if(HAS_BLOOD_DNA(src))
var/mutable_appearance/bloody_hands = mutable_appearance('icons/effects/blood.dmi', "bloodyhands")
+ if(species_fitted && icon_exists(bloody_hands.icon, "bloodyhands_[species_fitted]"))
+ bloody_hands.icon_state = "bloodyhands_[species_fitted]"
bloody_hands.color = get_blood_dna_color(return_blood_DNA())
. += bloody_hands
diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm
index bcf9327ed8000..72dc63c19166a 100644
--- a/code/modules/clothing/head/_head.dm
+++ b/code/modules/clothing/head/_head.dm
@@ -33,6 +33,8 @@
bloody_helmet = mutable_appearance('icons/effects/64x64.dmi', "helmetblood_large")
else
bloody_helmet = mutable_appearance('icons/effects/blood.dmi', "helmetblood")
+ if(species_fitted && icon_exists(bloody_helmet.icon, "helmetblood_[species_fitted]"))
+ bloody_helmet.icon_state = "helmetblood_[species_fitted]"
bloody_helmet.color = get_blood_dna_color(return_blood_DNA())
. += bloody_helmet
diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm
index 0bb43ccddeea2..771569397c8ca 100644
--- a/code/modules/clothing/masks/_masks.dm
+++ b/code/modules/clothing/masks/_masks.dm
@@ -38,6 +38,8 @@
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask")
if(HAS_BLOOD_DNA(src))
var/mutable_appearance/bloody_mask = mutable_appearance('icons/effects/blood.dmi', "maskblood")
+ if(species_fitted && icon_exists(bloody_mask.icon, "maskblood_[species_fitted]"))
+ bloody_mask.icon_state = "maskblood_[species_fitted]"
bloody_mask.color = get_blood_dna_color(return_blood_DNA())
. += bloody_mask
diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm
index 8e9af7a6a789b..9b5b599d1d1aa 100644
--- a/code/modules/clothing/masks/breath.dm
+++ b/code/modules/clothing/masks/breath.dm
@@ -42,7 +42,8 @@
/obj/item/clothing/mask/breath/examine(mob/user)
. = ..()
- . += span_notice("Alt-click [src] to adjust it.")
+ if(length(actions_types))
+ . += span_notice("Alt-click [src] to adjust it.")
/obj/item/clothing/mask/breath/medical
desc = "A close-fitting sterile mask that can be connected to an air supply."
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 8916c9fc230a9..f83ba0f70d3db 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -16,6 +16,8 @@
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask")
if(HAS_BLOOD_DNA(src))
var/mutable_appearance/bloody_mask = mutable_appearance('icons/effects/blood.dmi', "maskblood")
+ if(species_fitted && icon_exists(bloody_mask.icon, "maskblood_[species_fitted]"))
+ bloody_mask.icon_state = "maskblood_[species_fitted]"
bloody_mask.color = get_blood_dna_color(return_blood_DNA())
. += bloody_mask
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index a9aa009a16a44..d8560612d8e5c 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -54,6 +54,8 @@
bloody_shoes = mutable_appearance('icons/effects/64x64.dmi', "shoeblood_large")
else
bloody_shoes = mutable_appearance('icons/effects/blood.dmi', "shoeblood")
+ if(species_fitted && icon_exists(bloody_shoes.icon, "shoeblood_[species_fitted]"))
+ bloody_shoes.icon_state = "shoeblood_[species_fitted]"
bloody_shoes.color = get_blood_dna_color(return_blood_DNA())
. += bloody_shoes
diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm
index 112d2a0dc2b57..14f7f33bc0d0a 100644
--- a/code/modules/clothing/suits/_suits.dm
+++ b/code/modules/clothing/suits/_suits.dm
@@ -30,6 +30,8 @@
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
if(HAS_BLOOD_DNA(src))
var/mutable_appearance/bloody_armor = mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood")
+ if(species_fitted && icon_exists(bloody_armor.icon, "[bloody_armor.icon_state]_[species_fitted]"))
+ bloody_armor.icon_state = "[bloody_armor.icon_state]_[species_fitted]"
bloody_armor.color = get_blood_dna_color(return_blood_DNA())
. += bloody_armor
var/mob/living/carbon/human/M = loc
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 1bebc3b9dff3b..a909f3deb696b 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -372,7 +372,7 @@
/obj/item/clothing/head/hooded/human_head
name = "bloated human head"
desc = "A horribly bloated and mismatched human head."
- icon_state = "lingspacehelmet"
+ icon_state = "humanheadhat"
body_parts_covered = HEAD
flags_cover = HEADCOVERSEYES
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm
index 66ef8c2e4c66a..25aa84df63d10 100644
--- a/code/modules/clothing/under/_under.dm
+++ b/code/modules/clothing/under/_under.dm
@@ -33,6 +33,8 @@
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
if(HAS_BLOOD_DNA(src))
var/mutable_appearance/bloody_uniform = mutable_appearance('icons/effects/blood.dmi', "uniformblood")
+ if(species_fitted && icon_exists(bloody_uniform.icon, "uniformblood_[species_fitted]"))
+ bloody_uniform.icon_state = "uniformblood_[species_fitted]"
bloody_uniform.color = get_blood_dna_color(return_blood_DNA())
. += bloody_uniform
if(accessory_overlay)
diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm
index 31298dfc77ced..c5d616b1a3af7 100644
--- a/code/modules/food_and_drinks/food/snacks_pie.dm
+++ b/code/modules/food_and_drinks/food/snacks_pie.dm
@@ -43,10 +43,7 @@
if(ishuman(hit_atom))
var/mob/living/carbon/human/H = hit_atom
var/mutable_appearance/creamoverlay = mutable_appearance('icons/effects/creampie.dmi')
- if(H.dna.species.limbs_id == "lizard")
- creamoverlay.icon_state = "creampie_lizard"
- else
- creamoverlay.icon_state = "creampie_human"
+ creamoverlay.icon_state = H.dna.species.creampie_id
if(stunning)
H.Paralyze(20) //splat!
H.adjust_eye_blur(1)
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index c7fac4fd301db..38677ad30da7e 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -392,6 +392,7 @@ Key procs
/datum/language/bonespeak = list(LANGUAGE_ATOM),
/datum/language/sylvan = list(LANGUAGE_ATOM),
/datum/language/draconic = list(LANGUAGE_ATOM),
+ /datum/language/vox = list(LANGUAGE_ATOM),
/datum/language/machine = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
/datum/language/felinid = list(LANGUAGE_ATOM),
@@ -402,6 +403,7 @@ Key procs
/datum/language/bonespeak = list(LANGUAGE_ATOM),
/datum/language/sylvan = list(LANGUAGE_ATOM),
/datum/language/draconic = list(LANGUAGE_ATOM),
+ /datum/language/vox = list(LANGUAGE_ATOM),
/datum/language/machine = list(LANGUAGE_ATOM))
/datum/language_holder/empty
diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm
index 99171e324ffb1..71a46b48d5087 100644
--- a/code/modules/mob/living/brain/MMI.dm
+++ b/code/modules/mob/living/brain/MMI.dm
@@ -34,11 +34,10 @@
if(!brain)
icon_state = "mmi_off"
return
+ icon_state = brain.get_mmi_brain_sprite()
if(istype(brain, /obj/item/organ/brain/alien))
- icon_state = "mmi_brain_alien"
braintype = "Xenoborg" //HISS....Beep.
else
- icon_state = "mmi_brain"
braintype = "Cyborg"
/obj/item/mmi/update_overlays()
@@ -73,7 +72,7 @@
return
var/mob/living/brain/B = newbrain.brainmob
if(!B.key)
- B.notify_ghost_cloning("Someone has put your brain in a MMI!", source = src)
+ B.notify_ghost_cloning("Someone has put your [newbrain.brain_name] in an MMI!", source = src)
user.visible_message("[user] sticks \a [newbrain] into [src].", span_notice("[src]'s indicator light turn on as you insert [newbrain]."))
brainmob = newbrain.brainmob
@@ -130,14 +129,15 @@
/obj/item/mmi/attack_self(mob/user)
if(brain)
- user.visible_message(span_notice("[user] begins to remove the brain from [src]."), span_danger("You begin to pry the brain out of [src], ripping out the wires and probes."))
+ var/brain_flavor_name = brain.brain_name
+ user.visible_message(span_notice("[user] begins to remove the [brain_flavor_name] from [src]."), span_danger("You begin to pry the [brain_flavor_name] out of [src], ripping out the wires and probes."))
to_chat(brainmob, span_userdanger("You feel your mind failing as you are slowly ripped from the [src]."))
if(do_after(user, remove_time, src))
- to_chat(brainmob, span_userdanger("Due to the traumatic danger of your removal, all memories of the events leading to your brain being removed are lost[rebooting ? ", along with all memories of the events leading to your death as a cyborg." : ""]."))
+ to_chat(brainmob, span_userdanger("Due to the traumatic danger of your removal, all memories of the events leading to your [brain_flavor_name] being removed are lost[rebooting ? ", along with all memories of the events leading to your death as a cyborg." : ""]."))
eject_brain(user)
update_appearance(UPDATE_ICON)
name = initial(name)
- user.visible_message(span_notice("[user] rips the brain out of [src]."), span_danger("You successfully remove the brain from the [src][rebooting ? ", interrupting the reboot process." : ""]."))
+ user.visible_message(span_notice("[user] rips the [brain_flavor_name] out of [src]."), span_danger("You successfully remove the [brain_flavor_name] from the [src][rebooting ? ", interrupting the reboot process." : ""]."))
if(rebooting)
rebooting = FALSE
deltimer(reboot_timer)
@@ -192,7 +192,7 @@
brain = newbrain
else if(!brain)
brain = new(src)
- brain.name = "[L.real_name]'s brain"
+ brain.name = "[L.real_name]'s [brain.brain_name]"
name = "[initial(name)]: [brainmob.real_name]"
to_chat(brainmob, welcome_message)
@@ -262,13 +262,13 @@
if(brainmob)
var/mob/living/brain/B = brainmob
if(!B.key || !B.mind || B.stat == DEAD)
- . += span_warning("The MMI indicates the brain is completely unresponsive.")
+ . += span_warning("The MMI indicates the [brain.brain_name] is completely unresponsive.")
else if(!B.client)
- . += span_warning("The MMI indicates the brain is currently inactive; it might change.")
+ . += span_warning("The MMI indicates the [brain.brain_name] is currently inactive; it might change.")
else
- . += span_notice("The MMI indicates the brain is active.")
+ . += span_notice("The MMI indicates the [brain.brain_name] is active.")
. += span_notice("It has a port for reading AI law modules.")
if(laws)
. += span_notice("Any AI created using this MMI will use these uploaded laws:")
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index ec755053510e6..f3dd1fa5f7eb2 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -35,7 +35,7 @@
if(C.mind && C.mind.has_antag_datum(/datum/antagonist/changeling) && !no_id_transfer) //congrats, you're trapped in a body you don't control
if(brainmob && !(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_DEATHCOMA))))
- to_chat(brainmob, "You can't feel your body! You're still just a brain!")
+ to_chat(brainmob, span_danger("You can't feel your body! You're still just a [brain_name]!"))
forceMove(C)
C.update_hair()
return
@@ -90,7 +90,7 @@
..()
/obj/item/organ/brain/proc/transfer_identity(mob/living/L)
- name = "[L.name]'s brain"
+ name = "[L.name]'s [brain_name]"
if(brainmob || decoy_override)
return
if(!L.mind)
@@ -115,7 +115,7 @@
brainmob.set_species(ZI.old_species) //For if the brain is cloned
if(L.mind && L.mind.current)
L.mind.transfer_to(brainmob)
- to_chat(brainmob, span_notice("You feel slightly disoriented. That's normal when you're just a brain."))
+ to_chat(brainmob, span_notice("You feel slightly disoriented. That's normal when you're just a [brain_name]."))
/obj/item/organ/brain/attackby(obj/item/O, mob/user, params)
user.changeNext_move(CLICK_CD_MELEE)
@@ -138,7 +138,7 @@
to_chat(user, span_warning("You failed to pour [O] onto [src]!"))
return
- user.visible_message("[user] pours the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade of pink.", span_notice("You pour the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade of pink."))
+ user.visible_message("[user] pours the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade[brain_name == "brain" ? " of pink." : "."]", span_notice("You pour the contents of [O] onto [src], causing it to reform its original shape and turn a slightly brighter shade[brain_name == "brain" ? " of pink." : "."]"))
setOrganDamage(damage - (0.05 * maxHealth)) //heals a small amount, and by using "setorgandamage", we clear the failing variable if that was up
O.reagents.clear_reagents()
@@ -193,7 +193,7 @@
// This should be a better check but this covers 99.9% of cases
if(!(compatible_biotypes & C.mob_biotypes))
- to_chat(user, span_warner("This brain is incompatiable with this beings biology!"))
+ to_chat(user, span_warner("This brain is incompatible with this being's biology!"))
return
if(!target_has_brain && C.is_eyes_covered() && user.zone_selected == BODY_ZONE_HEAD)
@@ -226,7 +226,7 @@
/obj/item/organ/brain/on_life()
if(damage >= BRAIN_DAMAGE_DEATH) //rip
- to_chat(owner, span_userdanger("The last spark of life in your brain fizzles out..."))
+ to_chat(owner, span_userdanger("The last spark of life in your [brain_name] fizzles out..."))
owner.death()
brain_death = TRUE
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index 664adf0832a21..c1780eb93fa78 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -30,10 +30,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/grad_style
///The gradient color used to color the gradient.
var/grad_color
-
/// does it use skintones or not? (spoiler alert this is only used by humans)
var/use_skintones = FALSE
-
+ var/icon_husk
var/forced_skintone
/// What genders can this race be?
@@ -135,6 +134,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/grab_sound
///yogs - audio of a species' scream
var/screamsound //yogs - grabs scream from screamsound list or string
+ var/husk_color = "#A6A6A6"
+ var/creampie_id = "creampie_human"
/// The visual effect of the attack.
var/attack_effect = ATTACK_EFFECT_PUNCH
///is a flying species, just a check for some things
@@ -148,7 +149,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/species_gibs = "human"
/// Can this species use numbers in its name?
var/allow_numbers_in_name = FALSE
-
+ var/is_dimorphic = TRUE
/// species-only traits. Can be found in DNA.dm
var/list/species_traits = list()
/// generic traits tied to having the species
@@ -385,6 +386,10 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/obj/item/organ/tail/lizard/new_lizard_tail = neworgan
new_lizard_tail.tail_type = C.dna.features["tail_lizard"]
new_lizard_tail.spines = C.dna.features["spines"]
+ if(isvox(C))
+ var/obj/item/organ/tail/vox/new_vox_tail = neworgan
+ new_vox_tail.tail_type = C.dna.features["vox_skin_tone"]
+ new_vox_tail.tail_markings = C.dna.features["vox_tail_markings"]
// if(tail && (!should_have_tail || replace_current))
// tail.Remove(C,1)
@@ -497,7 +502,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
fly.Grant(C)
C.add_movespeed_modifier(MOVESPEED_ID_SPECIES, TRUE, 100, override=TRUE, multiplicative_slowdown=speedmod, movetypes=(~FLYING))
-
+ C.regenerate_icons()
SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
@@ -551,7 +556,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(!HD) //Decapitated
return
- if(HAS_TRAIT(H, TRAIT_HUSK))
+ if(HD.is_husked)
return
var/datum/sprite_accessory/S
var/list/standing = list()
@@ -581,6 +586,17 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(M.flags_inv & HIDEFACIALHAIR)
facialhair_hidden = TRUE
+ if(("vox_facial_quills" in H.dna.species.mutant_bodyparts) && !facialhair_hidden)
+ S = GLOB.vox_facial_quills_list[H.dna.features["vox_facial_quills"]]
+ if(S)
+ var/mutable_appearance/facial_quills_overlay = mutable_appearance(layer = -HAIR_LAYER, appearance_flags = KEEP_TOGETHER)
+ var/mutable_appearance/facial_quills_base = mutable_appearance(S.icon, S.icon_state)
+ facial_quills_base.color = forced_colour || H.facial_hair_color
+ if(S.color_blend_mode == COLOR_BLEND_ADD)
+ facial_quills_base.color = COLOR_MATRIX_ADD(facial_quills_base.color)
+ facial_quills_overlay.overlays += facial_quills_base
+ facial_quills_overlay.alpha = hair_alpha
+ standing += facial_quills_overlay
if(H.facial_hair_style && (FACEHAIR in species_traits) && (!facialhair_hidden || dynamic_fhair_suffix))
S = GLOB.facial_hair_styles_list[H.facial_hair_style]
if(S)
@@ -726,7 +742,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
hair_overlay.color = H.hair_color
hair_overlay.alpha = hair_alpha
standing+=hair_overlay
- //var/mutable_appearance/pod_flower = mutable_appearance(GLOB.pod_flower_list[H.dna.features["pod_flower"]].icon, GLOB.pod_flower_list[H.dna.features["pod_flower"]].icon_state, -HAIR_LAYER)
+ //var/mutable_appearance/pod_flower = mutable_appearance(GLOB.pod_flower_list[H.dna.features["pod_flower"]].icon, GLOB.pod_flower_list[H.dna.features["pod_flower"]].icon_state, -HAIR_LAYER)
S = GLOB.pod_flower_list[H.dna.features["pod_flower"]]
if(S)
var/flower_state = S.icon_state
@@ -746,6 +762,26 @@ GLOBAL_LIST_EMPTY(features_by_species)
flower_overlay.color = H.facial_hair_color
flower_overlay.alpha = hair_alpha
standing += flower_overlay
+ if(("vox_quills" in H.dna.species.mutant_bodyparts) && !hair_hidden)
+ S = GLOB.vox_quills_list[H.dna.features["vox_quills"]]
+ if(S)
+ var/mutable_appearance/quills_overlay = mutable_appearance(layer = -HAIR_LAYER, appearance_flags = KEEP_TOGETHER)
+ var/mutable_appearance/quills_base = mutable_appearance(S.icon, S.icon_state)
+ quills_base.color = forced_colour || H.hair_color
+ if(S.color_blend_mode == COLOR_BLEND_ADD)
+ quills_base.color = COLOR_MATRIX_ADD(quills_base.color)
+ quills_overlay.overlays += quills_base
+ //Gradients
+ grad_style = H.grad_style
+ grad_color = H.grad_color
+ if(grad_style)
+ var/datum/sprite_accessory/gradient = GLOB.hair_gradients_list[grad_style]
+ var/mutable_appearance/gradient_quills = mutable_appearance(gradient.icon, gradient.icon_state)
+ gradient_quills.color = COLOR_MATRIX_OVERLAY(grad_color)
+ gradient_quills.blend_mode = BLEND_INSET_OVERLAY
+ quills_overlay.overlays += gradient_quills
+ quills_overlay.alpha = hair_alpha
+ standing += quills_overlay
if(standing.len)
H.overlays_standing[HAIR_LAYER] = standing
@@ -758,7 +794,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
- if(HD && !(HAS_TRAIT(H, TRAIT_HUSK)))
+ if(HD && !HD.is_husked)
// lipstick
if(H.lip_style && (LIPS in species_traits))
var/mutable_appearance/lip_overlay = mutable_appearance('icons/mob/human_face.dmi', "lips_[H.lip_style]", -BODY_LAYER)
@@ -794,22 +830,34 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(HAS_TRAIT(H, TRAIT_SKINNY))
standing += wear_skinny_version(underwear.icon_state, underwear.icon, BODY_LAYER) //Neat, this works
else
- standing += mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
+ var/mutable_appearance/underwear_overlay = mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER)
+ if(H.dna.species.id in underwear.sprite_sheets)
+ if(icon_exists(underwear.sprite_sheets[H.dna.species.id], underwear.icon_state))
+ underwear_overlay.icon = underwear.sprite_sheets[H.dna.species.id]
+ standing += underwear_overlay
if(H.undershirt)
var/datum/sprite_accessory/undershirt/undershirt = GLOB.undershirt_list[H.undershirt]
if(undershirt)
if(HAS_TRAIT(H, TRAIT_SKINNY)) //Check for skinny first
standing += wear_skinny_version(undershirt.icon_state, undershirt.icon, BODY_LAYER)
- else if(H.gender == FEMALE && (FEMALE in possible_genders))
+ else if((H.gender == FEMALE && (FEMALE in possible_genders)) && H.dna.species.is_dimorphic)
standing += wear_female_version(undershirt.icon_state, undershirt.icon, BODY_LAYER)
else
- standing += mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
+ var/mutable_appearance/undershirt_overlay = mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER)
+ if(H.dna.species.id in undershirt.sprite_sheets)
+ if(icon_exists(undershirt.sprite_sheets[H.dna.species.id], undershirt.icon_state))
+ undershirt_overlay.icon = undershirt.sprite_sheets[H.dna.species.id]
+ standing += undershirt_overlay
if(H.socks && H.get_num_legs(FALSE) >= 2 && !(DIGITIGRADE in species_traits))
var/datum/sprite_accessory/socks/socks = GLOB.socks_list[H.socks]
if(socks)
- standing += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER)
+ var/mutable_appearance/socks_overlay = mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER)
+ if(H.dna.species.id in socks.sprite_sheets)
+ if(icon_exists(socks.sprite_sheets[H.dna.species.id], socks.icon_state))
+ socks_overlay.icon = socks.sprite_sheets[H.dna.species.id]
+ standing += socks_overlay
if(standing.len)
H.overlays_standing[BODY_LAYER] = standing
@@ -819,6 +867,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
/datum/species/proc/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour)
var/list/bodyparts_to_add = mutant_bodyparts.Copy()
+ if(HAS_TRAIT(H, TRAIT_HUSK) && length(parts_to_husk))
+ bodyparts_to_add &= parts_to_husk
var/list/relevent_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
var/list/standing = list()
@@ -930,10 +980,10 @@ GLOBAL_LIST_EMPTY(features_by_species)
if((H.wear_mask && (H.wear_mask.flags_inv & HIDEEYES)) || (H.head && (H.head.flags_inv & HIDEEYES)) || !HD)
bodyparts_to_add -= "preternis_eye"
+
if("preternis_core" in mutant_bodyparts)
if(!get_location_accessible(H, BODY_ZONE_CHEST))
bodyparts_to_add -= "preternis_core"
-
if("pod_hair" in mutant_bodyparts)
if((H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || (H.head && (H.head.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "pod_hair"
@@ -944,6 +994,26 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(H.dna.features["pod_flower"] != H.dna.features["pod_hair"])
H.dna.features["pod_flower"] = H.dna.features["pod_hair"]
+ if("vox_tail" in mutant_bodyparts)
+ if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+ bodyparts_to_add -= "vox_tail"
+
+ if("wagging_vox_tail" in mutant_bodyparts)
+ if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+ bodyparts_to_add -= "wagging_vox_tail"
+ else if ("vox_tail" in mutant_bodyparts)
+ bodyparts_to_add -= "wagging_vox_tail"
+
+ if("vox_tail_markings" in mutant_bodyparts)
+ if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+ bodyparts_to_add -= "vox_tail_markings"
+
+ if("wagging_vox_tail_markings" in mutant_bodyparts)
+ if(!H.dna.features["vox_tail_markings"] || H.dna.features["vox_tail_markings"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+ bodyparts_to_add -= "vox_tail_markings"
+ else if ("vox_tail" in mutant_bodyparts)
+ bodyparts_to_add -= "wagging_vox_tail_markings"
+
//Digitigrade legs are stuck in the phantom zone between true limbs and mutant bodyparts. Mainly it just needs more agressive updating than most limbs.
var/update_needed = FALSE
var/not_digitigrade = TRUE
@@ -982,7 +1052,6 @@ GLOBAL_LIST_EMPTY(features_by_species)
H.update_body_parts()
if(not_digitigrade && (DIGITIGRADE in species_traits)) //Curse is lifted
species_traits -= DIGITIGRADE
-
if(!bodyparts_to_add)
return
@@ -1056,15 +1125,33 @@ GLOBAL_LIST_EMPTY(features_by_species)
S = GLOB.ipc_antennas_list[H.dna.features["ipc_antenna"]]
if("ipc_chassis")
S = GLOB.ipc_chassis_list[H.dna.features["ipc_chassis"]]
+ if("vox_tail")
+ var/obj/item/organ/tail/vox/vox_tail = H.getorganslot(ORGAN_SLOT_TAIL)
+ if(vox_tail && istype(vox_tail))
+ S = GLOB.vox_tails_list[vox_tail.tail_type]
+ if("wagging_vox_tail")
+ var/obj/item/organ/tail/vox/vox_tail = H.getorganslot(ORGAN_SLOT_TAIL)
+ if(vox_tail && istype(vox_tail))
+ S = GLOB.animated_vox_tails_list[vox_tail.tail_type]
+ if("vox_body_markings")
+ S = GLOB.vox_body_markings_list[H.dna.features["vox_body_markings"]]
+ if("vox_tail_markings")
+ var/obj/item/organ/tail/vox/vox_tail = H.getorganslot(ORGAN_SLOT_TAIL)
+ if(vox_tail && istype(vox_tail))
+ S = GLOB.vox_tail_markings_list[vox_tail.tail_markings]
+ if("wagging_vox_tail_markings")
+ var/obj/item/organ/tail/vox/vox_tail = H.getorganslot(ORGAN_SLOT_TAIL)
+ if(vox_tail && istype(vox_tail))
+ S = GLOB.animated_vox_tail_markings_list[vox_tail.tail_markings]
if(!S || S.icon_state == "none")
continue
var/mutable_appearance/accessory_overlay = mutable_appearance(S.icon, layer = -layer)
//A little rename so we don't have to use tail_lizard or tail_human when naming the sprites.
- if(bodypart == "tail_lizard" || bodypart == "tail_human" || bodypart == "tail_polysmorph")
+ if(bodypart == "tail_lizard" || bodypart == "tail_human" || bodypart == "tail_polysmorph" || bodypart == "vox_tail")
bodypart = "tail"
- else if(bodypart == "waggingtail_lizard" || bodypart == "waggingtail_human")
+ else if(bodypart == "waggingtail_lizard" || bodypart == "waggingtail_human" || bodypart == "wagging_vox_tail")
bodypart = "waggingtail"
if(S.gender_specific)
@@ -1085,6 +1172,11 @@ GLOBAL_LIST_EMPTY(features_by_species)
accessory_overlay.color = fixed_mut_color
else //Then snowflake color
accessory_overlay.color = H.dna.features["mcolor"]
+ if(MUTCOLORS_SECONDARY)
+ if(fixed_mut_color)
+ accessory_overlay.color = fixed_mut_color
+ else
+ accessory_overlay.color = H.dna.features["mcolor_secondary"]
if(HAIR)
if(hair_color == "mutcolor")
accessory_overlay.color = H.dna.features["mcolor"]
@@ -1098,6 +1190,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
accessory_overlay.color = H.eye_color
else
accessory_overlay.color = forced_colour
+ if(S.color_blend_mode == COLOR_BLEND_ADD)
+ accessory_overlay.color = COLOR_MATRIX_ADD(accessory_overlay.color)
standing += accessory_overlay
if(S.emissive && !(HAS_TRAIT(H, TRAIT_HUSK)) && !istype(H, /mob/living/carbon/human/dummy))//don't put emissives on dummy mobs as they're used for the preference menu, which doesn't draw emissives properly
@@ -1136,6 +1230,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
emissive_accessory_overlay.color = forced_colour
standing += emissive_accessory_overlay
+ if(length(S.body_slots) || length(S.external_slots))
+ standing += return_accessory_layer(layer, S, H, accessory_overlay.color)
if(S.hasinner)
var/mutable_appearance/inner_accessory_overlay = mutable_appearance(S.icon, layer = -layer)
if(S.gender_specific)
@@ -1147,7 +1243,10 @@ GLOBAL_LIST_EMPTY(features_by_species)
inner_accessory_overlay = center_image(inner_accessory_overlay, S.dimension_x, S.dimension_y)
standing += inner_accessory_overlay
-
+ if(HAS_TRAIT(H, TRAIT_HUSK))
+ for(var/image/sprite_image as anything in standing)
+ huskify_image(sprite_image, H, draw_blood = FALSE)
+ sprite_image.color = H.dna.species.husk_color
H.overlays_standing[layer] = standing.Copy()
standing = list()
@@ -1155,7 +1254,6 @@ GLOBAL_LIST_EMPTY(features_by_species)
H.apply_overlay(BODY_ADJ_LAYER)
H.apply_overlay(BODY_FRONT_LAYER)
-
//This exists so sprite accessories can still be per-layer without having to include that layer's
//number in their sprite name, which causes issues when those numbers change.
/datum/species/proc/mutant_bodyparts_layertext(layer)
@@ -2215,10 +2313,10 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(H.IsParalyzed() || H.IsStun())
return FALSE
// var/obj/item/organ/tail = H.getorganslot(ORGAN_SLOT_TAIL)
- return ("tail_human" in mutant_bodyparts) || ("waggingtail_human" in mutant_bodyparts) || ("tail_lizard" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts)
+ return ("tail_human" in mutant_bodyparts) || ("waggingtail_human" in mutant_bodyparts) || ("tail_lizard" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts) || ("vox_tail" in mutant_bodyparts) || ("wagging_vox_tail" in mutant_bodyparts)
/datum/species/proc/is_wagging_tail(mob/living/carbon/human/H)
- return ("waggingtail_human" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts)
+ return ("waggingtail_human" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts) || ("wagging_vox_tail" in mutant_bodyparts)
/datum/species/proc/start_wagging_tail(mob/living/carbon/human/H)
if("tail_human" in mutant_bodyparts)
@@ -2229,6 +2327,11 @@ GLOBAL_LIST_EMPTY(features_by_species)
mutant_bodyparts -= "spines"
mutant_bodyparts |= "waggingtail_lizard"
mutant_bodyparts |= "waggingspines"
+ if("vox_tail" in mutant_bodyparts)
+ mutant_bodyparts -= "vox_tail"
+ mutant_bodyparts -= "vox_tail_markings"
+ mutant_bodyparts |= "wagging_vox_tail"
+ mutant_bodyparts |= "wagging_vox_tail_markings"
H.update_body()
/datum/species/proc/stop_wagging_tail(mob/living/carbon/human/H)
@@ -2240,6 +2343,11 @@ GLOBAL_LIST_EMPTY(features_by_species)
mutant_bodyparts -= "waggingspines"
mutant_bodyparts |= "tail_lizard"
mutant_bodyparts |= "spines"
+ if("wagging_vox_tail" in mutant_bodyparts)
+ mutant_bodyparts |= "vox_tail"
+ mutant_bodyparts |= "vox_tail_markings"
+ mutant_bodyparts -= "wagging_vox_tail"
+ mutant_bodyparts -= "wagging_vox_tail_markings"
H.update_body()
///////////////
@@ -2362,6 +2470,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
if(HAS_BONE in species_traits)
. |= BIO_JUST_BONE
+/datum/species/proc/get_footprint_sprite()
+ return null
+
/datum/species/proc/eat_text(fullness, eatverb, obj/O, mob/living/carbon/C, mob/user)
. = TRUE
if(C == user)
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index f4b38a0eb575d..8736b752f2a77 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -80,6 +80,7 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
target.dna.features["frills"] = "None"
target.dna.features["horns"] = "None"
target.dna.features["mcolor"] = COLOR_VIBRANT_LIME
+ target.dna.features["mcolor_secondary"] = COLOR_RED
target.dna.features["moth_antennae"] = "Plain"
target.dna.features["moth_markings"] = "None"
target.dna.features["moth_wings"] = "Plain"
@@ -88,6 +89,11 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
target.dna.features["tail_cat"] = "None"
target.dna.features["tail_lizard"] = "Smooth"
target.dna.features["pod_hair"] = "Ivy"
+ target.dna.features["vox_quills"] = "None"
+ target.dna.features["vox_facial_quills"] = "None"
+ target.dna.features["vox_skin_tone"] = "lime"
+ target.dna.features["vox_tail_markings"] = "None"
+ target.dna.features["vox_body_markings"] = "None"
/// Provides a dummy that is consistently bald, white, naked, etc.
/mob/living/carbon/human/dummy/consistent
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 9c9526e2924c6..af39fdd088d5d 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -420,7 +420,14 @@
/mob/living/carbon/human/get_footprint_sprite()
var/obj/item/bodypart/l_leg/left_leg = get_bodypart(BODY_ZONE_L_LEG)
var/obj/item/bodypart/r_leg/right_leg = get_bodypart(BODY_ZONE_R_LEG)
- return shoes?.footprint_sprite || left_leg?.footprint_sprite || right_leg?.footprint_sprite
+ var/species_id
+ var/datum/species/species
+ if(left_leg?.species_id == right_leg?.species_id)
+ species_id = left_leg.species_id
+ var/species_type = GLOB.species_list[species_id]
+ if(species_type)
+ species = new species_type()
+ return species?.get_footprint_sprite() || shoes?.footprint_sprite || left_leg?.footprint_sprite || right_leg?.footprint_sprite
/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null)
if(judgement_criteria & JUDGE_EMAGGED)
@@ -685,6 +692,7 @@
if(creamed) //clean both to prevent a rare bug
cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_lizard"))
cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_human"))
+ cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_vox"))
creamed = FALSE
//Turns a mob black, flashes a skeleton overlay
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 7ca856b5669c7..7a676d6667e6d 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -20,6 +20,7 @@
attack_verbs = list("slash", "scratch", "claw")
attack_effect = ATTACK_EFFECT_CLAW
barefoot_step_sound = FOOTSTEP_MOB_CLAW
+ creampie_id = "creampie_lizard"
attack_sound = 'sound/weapons/slash.ogg'
miss_sound = 'sound/weapons/slashmiss.ogg'
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/lizard
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index 792a3f98f72ce..acc4dde95a25f 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -28,7 +28,8 @@
liked_food = DAIRY
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC
species_language_holder = /datum/language_holder/plasmaman
-
+ survival_box_replacements = list(items_to_delete = list(/obj/item/clothing/mask/breath, /obj/item/tank/internals/emergency_oxygen),\
+ new_items = list(/obj/item/tank/internals/plasmaman/belt))
screamsound = list('sound/voice/plasmaman/plasmeme_scream_1.ogg', 'sound/voice/plasmaman/plasmeme_scream_2.ogg', 'sound/voice/plasmaman/plasmeme_scream_3.ogg')
smells_like = "plasma-caked calcium"
diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm
index d00c376059650..de86f33fcdcb0 100644
--- a/code/modules/mob/living/carbon/human/status_procs.dm
+++ b/code/modules/mob/living/carbon/human/status_procs.dm
@@ -32,7 +32,7 @@
update_hair()
/mob/living/carbon/human/become_husk(source)
- if(NOHUSK in dna.species.species_traits)
+ if(NOHUSK in dna?.species?.species_traits)
cure_husk()
return
. = ..()
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 2991dbe2b0ccb..a892565fa24fe 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -155,17 +155,36 @@ There are several things that need to be remembered:
//Friendly reminder that icon_exists(file, state, scream = TRUE) is your friend when debugging this code.
var/icon_file
var/target_overlay = RESOLVE_ICON_STATE(uniform) //Selects proper icon from the vars the clothing has (Search define for more.)
-
+ var/obj/item/bodypart/l_leg = get_bodypart(BODY_ZONE_L_LEG)
+ var/obj/item/bodypart/r_leg = get_bodypart(BODY_ZONE_R_LEG)
+ var/obj/item/bodypart/chest/chest = get_bodypart(BODY_ZONE_CHEST)
+ uniform.species_fitted = null
if(uniform.adjusted == ALT_STYLE)
target_overlay = "[target_overlay]_d"
else if(uniform.adjusted == DIGITIGRADE_STYLE) // yogs - digitigrade alt sprites
target_overlay = "[target_overlay]_l"
else if(uniform.adjusted == DIGIALT_STYLE)
target_overlay = "[target_overlay]_d_l" // yogs end
+ //Checks for GAGS
+ if(uniform.greyscale_config && uniform.greyscale_colors)
+ if("GAGS_sprite" in uniform.sprite_sheets)
+ var/list/GAGS_species = uniform.sprite_sheets["GAGS_sprite"]
+ if((SPECIES_VOX in GAGS_species) && l_leg?.species_id == SPECIES_VOX && r_leg?.species_id == SPECIES_VOX)
+ target_overlay += "_vox"
+ uniform.species_fitted = SPECIES_VOX
+ if(l_leg?.species_id == SPECIES_VOX && (r_leg?.species_id == SPECIES_VOX))//for Vox, it's the Vox legs that make regular sprites not fit
+ if(SPECIES_VOX in uniform.sprite_sheets)
+ if(icon_exists(uniform.sprite_sheets[SPECIES_VOX], uniform.icon_state))
+ icon_file = uniform.sprite_sheets[SPECIES_VOX]
+ uniform.species_fitted = SPECIES_VOX
+ else if(chest?.species_id in uniform.sprite_sheets)
+ if(icon_exists(uniform.sprite_sheets[chest.species_id], uniform.icon_state))
+ icon_file = uniform.sprite_sheets[chest.species_id]
+ uniform.species_fitted = chest.species_id
var/mutable_appearance/uniform_overlay
- if(gender == FEMALE && uniform.fitted != NO_FEMALE_UNIFORM)
+ if((gender == FEMALE && dna.species.is_dimorphic) && uniform.fitted != NO_FEMALE_UNIFORM)
uniform_overlay = uniform.build_worn_icon(
default_layer = UNIFORM_LAYER,
default_icon_file = icon_file,
@@ -173,6 +192,7 @@ There are several things that need to be remembered:
femaleuniform = uniform.fitted,
override_state = target_overlay,
)
+
else
uniform_overlay = uniform.build_worn_icon(
default_layer = UNIFORM_LAYER,
@@ -223,13 +243,21 @@ There are several things that need to be remembered:
var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_GLOVES) + 1]
inv.update_appearance(UPDATE_ICON)
+ var/obj/item/bodypart/l_arm = get_bodypart(BODY_ZONE_L_ARM)
+ var/obj/item/bodypart/r_arm = get_bodypart(BODY_ZONE_R_ARM)
if(!gloves && blood_in_hands)
var/mutable_appearance/bloody_overlay = mutable_appearance('icons/effects/blood.dmi', "bloodyhands", -GLOVES_LAYER)
+ if(icon_exists(bloody_overlay.icon, "[bloody_overlay.icon_state]_[l_arm?.species_id]"))
+ bloody_overlay.icon_state = "bloodyhands_[l_arm.species_id]"
if(get_num_arms(FALSE) < 2)
if(has_left_hand(FALSE))
bloody_overlay.icon_state = "bloodyhands_left"
+ if(icon_exists(bloody_overlay.icon, "[bloody_overlay.icon_state]_[l_arm?.species_id]"))
+ bloody_overlay.icon_state += "_[l_arm.species_id]"
else if(has_right_hand(FALSE))
bloody_overlay.icon_state = "bloodyhands_right"
+ if(icon_exists(bloody_overlay.icon, "[bloody_overlay.icon_state]_[r_arm?.species_id]"))
+ bloody_overlay.icon_state += "_[r_arm.species_id]"
bloody_overlay.color = get_blood_dna_color(return_blood_DNA())
overlays_standing[GLOVES_LAYER] = bloody_overlay
@@ -241,7 +269,14 @@ There are several things that need to be remembered:
if(hud_used.inventory_shown)
client.screen += gloves
update_observer_view(gloves,1)
- overlays_standing[GLOVES_LAYER] = gloves.build_worn_icon(default_layer = GLOVES_LAYER, default_icon_file = 'icons/mob/clothing/hands/hands.dmi')
+ var/icon_to_use = 'icons/mob/clothing/hands/hands.dmi'
+ gloves.species_fitted = null
+ if(l_arm?.species_id == r_arm?.species_id)
+ if(l_arm.species_id in gloves.sprite_sheets)
+ if(icon_exists(gloves.sprite_sheets[l_arm.species_id], gloves.icon_state))
+ icon_to_use = gloves.sprite_sheets[l_arm.species_id]
+ gloves.species_fitted = l_arm.species_id
+ overlays_standing[GLOVES_LAYER] = gloves.build_worn_icon(default_layer = GLOVES_LAYER, default_icon_file = icon_to_use)
gloves_overlay = overlays_standing[GLOVES_LAYER]
if(OFFSET_GLOVES in dna.species.offset_features)
gloves_overlay.pixel_x += dna.species.offset_features[OFFSET_GLOVES][1]
@@ -253,7 +288,8 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_glasses()
remove_overlay(GLASSES_LAYER)
- if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated
+ var/obj/item/bodypart/head/head = get_bodypart(BODY_ZONE_HEAD)
+ if(!head) //decapitated
return
if(client && hud_used)
@@ -267,7 +303,13 @@ There are several things that need to be remembered:
client.screen += glasses //Either way, add the item to the HUD
update_observer_view(glasses,1)
if(!(head && (head.flags_inv & HIDEEYES)) && !(wear_mask && (wear_mask.flags_inv & HIDEEYES)))
- overlays_standing[GLASSES_LAYER] = glasses.build_worn_icon(default_layer = GLASSES_LAYER, default_icon_file = 'icons/mob/clothing/eyes/eyes.dmi')
+ var/icon_to_use = 'icons/mob/clothing/eyes/eyes.dmi'
+ glasses.species_fitted = null
+ if(head.species_id in glasses.sprite_sheets)
+ if(icon_exists(glasses.sprite_sheets[head.species_id], glasses.icon_state))
+ icon_to_use = glasses.sprite_sheets[head.species_id]
+ glasses.species_fitted = head.species_id
+ overlays_standing[GLASSES_LAYER] = glasses.build_worn_icon(default_layer = GLASSES_LAYER, default_icon_file = icon_to_use)
var/mutable_appearance/glasses_overlay = overlays_standing[GLASSES_LAYER]
if(glasses_overlay)
@@ -281,7 +323,8 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_ears()
remove_overlay(EARS_LAYER)
- if(!get_bodypart(BODY_ZONE_HEAD)) //decapitated
+ var/obj/item/bodypart/head/head = get_bodypart(BODY_ZONE_HEAD)
+ if(!head) //decapitated
return
if(client && hud_used)
@@ -294,7 +337,13 @@ There are several things that need to be remembered:
if(hud_used.inventory_shown) //if the inventory is open
client.screen += ears //add it to the client's screen
update_observer_view(ears,1)
- overlays_standing[EARS_LAYER] = ears.build_worn_icon(default_layer = EARS_LAYER, default_icon_file = 'icons/mob/clothing/ears/ears.dmi')
+ var/icon_to_use = 'icons/mob/clothing/ears/ears.dmi'
+ ears.species_fitted = null
+ if(head.species_id in ears.sprite_sheets)
+ if(icon_exists(ears.sprite_sheets[head.species_id], ears.icon_state))
+ icon_to_use = ears.sprite_sheets[head.species_id]
+ ears.species_fitted = head.species_id
+ overlays_standing[EARS_LAYER] = ears.build_worn_icon(default_layer = EARS_LAYER, default_icon_file = icon_to_use)
var/mutable_appearance/ears_overlay = overlays_standing[EARS_LAYER]
if(OFFSET_EARS in dna.species.offset_features)
ears_overlay.pixel_x += dna.species.offset_features[OFFSET_EARS][1]
@@ -336,16 +385,29 @@ There are several things that need to be remembered:
if(shoes)
var/target_overlay = RESOLVE_ICON_STATE(shoes)
+ var/obj/item/bodypart/l_leg = get_bodypart(BODY_ZONE_L_LEG)
+ var/obj/item/bodypart/r_leg = get_bodypart(BODY_ZONE_R_LEG)
+ shoes.species_fitted = null
if(istype(shoes, /obj/item/clothing/shoes))
var/obj/item/clothing/shoes/S = shoes
if(S.adjusted == DIGITIGRADE_STYLE)
target_overlay = "[target_overlay]_l"
+ if("GAGS_sprite" in S.sprite_sheets)
+ var/list/GAGS_species = S.sprite_sheets["GAGS_sprite"]
+ if((l_leg?.species_id == r_leg?.species_id) && (l_leg.species_id in GAGS_species))
+ target_overlay += "_[l_leg.species_id]"
+ S.species_fitted = l_leg.species_id
shoes.screen_loc = ui_shoes //move the item to the appropriate screen loc
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown) //if the inventory is open
client.screen += shoes //add it to client's screen
update_observer_view(shoes,1)
- overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(default_layer = SHOES_LAYER, default_icon_file = 'icons/mob/clothing/feet/feet.dmi', override_state = target_overlay)
+ var/icon_to_use = DEFAULT_SHOES_FILE
+ if((l_leg?.species_id == r_leg?.species_id) && (l_leg.species_id in shoes.sprite_sheets))
+ if(icon_exists(shoes.sprite_sheets[l_leg.species_id], shoes.icon_state))
+ icon_to_use = shoes.sprite_sheets[l_leg.species_id]
+ shoes.species_fitted = l_leg.species_id
+ overlays_standing[SHOES_LAYER] = shoes.build_worn_icon(default_layer = SHOES_LAYER, default_icon_file = icon_to_use, override_state = target_overlay)
var/mutable_appearance/shoes_overlay = overlays_standing[SHOES_LAYER]
if(OFFSET_SHOES in dna.species.offset_features)
shoes_overlay.pixel_x += dna.species.offset_features[OFFSET_SHOES][1]
@@ -380,18 +442,25 @@ There are several things that need to be remembered:
/mob/living/carbon/human/update_inv_head()
- ..()
- update_mutant_bodyparts()
+ remove_overlay(HEAD_LAYER)
+ if(client && hud_used?.inv_slots[TOBITSHIFT(ITEM_SLOT_BACK) + 1])
+ var/atom/movable/screen/inventory/inv = hud_used.inv_slots[TOBITSHIFT(ITEM_SLOT_HEAD) + 1]
+ inv.update_appearance(UPDATE_ICON)
if(head)
update_hud_head(head)
- overlays_standing[HEAD_LAYER] = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/clothing/head/head.dmi')
- var/mutable_appearance/head_overlay = overlays_standing[HEAD_LAYER]
- if(head_overlay)
- remove_overlay(HEAD_LAYER)
+ var/obj/item/bodypart/head/head_bodypart = get_bodypart(BODY_ZONE_HEAD)
+ var/icon_to_use = 'icons/mob/clothing/head/head.dmi'
+ head.species_fitted = null
+ if(head_bodypart?.species_id in head.sprite_sheets)
+ if(icon_exists(head.sprite_sheets[head_bodypart.species_id], head.icon_state))
+ icon_to_use = head.sprite_sheets[head_bodypart.species_id]
+ head.species_fitted = head_bodypart.species_id
+ overlays_standing[HEAD_LAYER] = head.build_worn_icon(default_layer = HEAD_LAYER, default_icon_file = icon_to_use)
+ var/mutable_appearance/head_overlay = overlays_standing[HEAD_LAYER]
if(OFFSET_HEAD in dna.species.offset_features)
head_overlay.pixel_x += dna.species.offset_features[OFFSET_HEAD][1]
head_overlay.pixel_y += dna.species.offset_features[OFFSET_HEAD][2]
- overlays_standing[HEAD_LAYER] = head_overlay
+ update_mutant_bodyparts()
apply_overlay(HEAD_LAYER)
/mob/living/carbon/human/update_inv_belt()
@@ -406,7 +475,14 @@ There are several things that need to be remembered:
if(client && hud_used && hud_used.hud_shown)
client.screen += belt
update_observer_view(belt)
- overlays_standing[BELT_LAYER] = belt.build_worn_icon(default_layer = BELT_LAYER, default_icon_file = 'icons/mob/clothing/belt.dmi')
+ var/obj/item/bodypart/chest/chest = get_bodypart(BODY_ZONE_CHEST)
+ var/icon_to_use = 'icons/mob/clothing/belt.dmi'
+ belt.species_fitted = null
+ if(chest?.species_id in belt.sprite_sheets)
+ if(icon_exists(belt.sprite_sheets[chest.species_id], belt.icon_state))
+ icon_to_use = belt.sprite_sheets[chest.species_id]
+ belt.species_fitted = chest.species_id
+ overlays_standing[BELT_LAYER] = belt.build_worn_icon(default_layer = BELT_LAYER, default_icon_file = icon_to_use)
var/mutable_appearance/belt_overlay = overlays_standing[BELT_LAYER]
if(OFFSET_BELT in dna.species.offset_features)
belt_overlay.pixel_x += dna.species.offset_features[OFFSET_BELT][1]
@@ -433,7 +509,20 @@ There are several things that need to be remembered:
if(client && hud_used && hud_used.hud_shown)
if(hud_used.inventory_shown)
client.screen += wear_suit
- overlays_standing[SUIT_LAYER] = wear_suit.build_worn_icon(default_layer = SUIT_LAYER, default_icon_file = 'icons/mob/clothing/suit/suit.dmi', override_state = worn_suit_icon)
+ var/obj/item/bodypart/chest/chest = get_bodypart(BODY_ZONE_CHEST)
+ var/icon_to_use = DEFAULT_SUIT_FILE
+ S.species_fitted = null
+ var/obj/item/bodypart/l_leg = get_bodypart(BODY_ZONE_L_LEG)
+ var/obj/item/bodypart/r_leg = get_bodypart(BODY_ZONE_R_LEG)
+ if(l_leg?.species_id == r_leg?.species_id == SPECIES_VOX)//for Vox, it's the Vox legs that make regular sprites not fit
+ if(icon_exists(S.sprite_sheets[l_leg.species_id], S.icon_state))
+ icon_to_use = S.sprite_sheets[l_leg.species_id]
+ S.species_fitted = l_leg.species_id
+ else if(chest?.species_id in S.sprite_sheets)
+ if(icon_exists(S.sprite_sheets[chest.species_id], S.icon_state))
+ icon_to_use = S.sprite_sheets[chest.species_id]
+ S.species_fitted = chest.species_id
+ overlays_standing[SUIT_LAYER] = wear_suit.build_worn_icon(default_layer = SUIT_LAYER, default_icon_file = icon_to_use, override_state = worn_suit_icon)
var/mutable_appearance/suit_overlay = overlays_standing[SUIT_LAYER]
if(OFFSET_SUIT in dna.species.offset_features)
suit_overlay.pixel_x += dna.species.offset_features[OFFSET_SUIT][1]
@@ -487,7 +576,14 @@ There are several things that need to be remembered:
target_overlay = "[target_overlay]_l"
update_hud_wear_mask(wear_mask)
if(!(head && (head.flags_inv & HIDEMASK)))
- overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/clothing/mask/mask.dmi', override_state = target_overlay)
+ var/obj/item/bodypart/head/head_bodypart = get_bodypart(BODY_ZONE_HEAD)
+ var/icon_to_use = 'icons/mob/clothing/mask/mask.dmi'
+ wear_mask.species_fitted = null
+ if(head_bodypart.species_id in wear_mask.sprite_sheets)
+ if(icon_exists(wear_mask.sprite_sheets[head_bodypart.species_id], wear_mask.icon_state))
+ icon_to_use = wear_mask.sprite_sheets[head_bodypart.species_id]
+ wear_mask.species_fitted = head_bodypart.species_id
+ overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(default_layer = FACEMASK_LAYER, default_icon_file = icon_to_use, override_state = target_overlay)
var/mutable_appearance/mask_overlay = overlays_standing[FACEMASK_LAYER]
if(mask_overlay)
remove_overlay(FACEMASK_LAYER)
@@ -507,7 +603,14 @@ There are several things that need to be remembered:
if(back)
update_hud_back(back)
- overlays_standing[BACK_LAYER] = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = 'icons/mob/clothing/back.dmi')
+ var/obj/item/bodypart/chest/chest = get_bodypart(BODY_ZONE_CHEST)
+ var/icon_to_use = 'icons/mob/clothing/back.dmi'
+ back.species_fitted = null
+ if(chest?.species_id in back.sprite_sheets)
+ if(icon_exists(back.sprite_sheets[chest.species_id], back.icon_state))
+ icon_to_use = back.sprite_sheets[chest.species_id]
+ back.species_fitted = chest.species_id
+ overlays_standing[BACK_LAYER] = back.build_worn_icon(default_layer = BACK_LAYER, default_icon_file = icon_to_use)
var/mutable_appearance/back_overlay = overlays_standing[BACK_LAYER]
if(back_overlay)
remove_overlay(BACK_LAYER)
@@ -614,12 +717,16 @@ generate/load female uniform sprites matching all previously decided variables
t_state = !isinhands ? (worn_icon_state ? worn_icon_state : icon_state) : (item_state ? item_state : icon_state)
//Find a valid icon file from variables+arguments
- var/file2use = !isinhands ? (worn_icon ? worn_icon : default_icon_file) : default_icon_file
+ var/file2use = default_icon_file
+ if(!isinhands && worn_icon)
+ if(!species_fitted || (species_fitted && greyscale_config))
+ file2use = worn_icon
//Find a valid layer from variables+arguments
var/layer2use = alternate_worn_layer ? alternate_worn_layer : default_layer
var/mob/living/carbon/human/H = loc
+
var/mutable_appearance/standing
if(femaleuniform)
if(HAS_TRAIT(H, TRAIT_SKINNY) && (H.underwear == "Nude"))
@@ -680,12 +787,15 @@ generate/load female uniform sprites matching all previously decided variables
. += "-coloured-[dna.species.forced_skintone]"
else if(dna.species.fixed_mut_color)
. += "-coloured-[dna.species.fixed_mut_color]"
+ else if(dna.species.get_icon_variant(src))
+ . += "-limb-variant-[dna.species.get_icon_variant(src)]"
else if(dna.features["mcolor"])
. += "-coloured-[dna.features["mcolor"]]"
else
. += "-not_coloured"
- . += "-[gender]"
+ if(dna.species.is_dimorphic)
+ . += "-[gender]"
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
@@ -701,6 +811,11 @@ generate/load female uniform sprites matching all previously decided variables
. += "-digitigrade[BP.use_digitigrade]"
if(BP.dmg_overlay_type)
. += "-[BP.dmg_overlay_type]"
+ if(BP.has_static_sprite_part)
+ var/static_text = "-static"
+ if(BP.limb_icon_variant in dna.species.get_special_statics())
+ static_text += "-special-[BP.limb_icon_variant]"
+ . += static_text
if(HAS_TRAIT(src, TRAIT_HUSK))
. += "-husk"
@@ -772,3 +887,4 @@ generate/load female uniform sprites matching all previously decided variables
update_inv_wear_mask()
#undef RESOLVE_ICON_STATE
+
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 3c6c6731fd173..09ee7a2554b48 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -490,9 +490,9 @@
. += "-organic"
else
. += "-robotic"
+ if(BP.is_husked)
+ . += "-husk"
- if(HAS_TRAIT(src, TRAIT_HUSK))
- . += "-husk"
//change the mob's icon to the one matching its key
diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm
index 7bcec196672f3..2e1cfeaeb9c41 100644
--- a/code/modules/reagents/reagent_containers/blood_pack.dm
+++ b/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -102,7 +102,7 @@
/obj/item/reagent_containers/blood/random/Initialize(mapload)
icon_state = "bloodpack"
- blood_type = pick("A+", "A-", "B+", "B-", "O+", "O-", "L")
+ blood_type = pick("A+", "A-", "B+", "B-", "O+", "O-", "L", "V")
return ..()
/obj/item/reagent_containers/blood/APlus
diff --git a/code/modules/research/designs/limbgrower_designs.dm b/code/modules/research/designs/limbgrower_designs.dm
index ce5690cd7e5e6..9ea17d9001685 100644
--- a/code/modules/research/designs/limbgrower_designs.dm
+++ b/code/modules/research/designs/limbgrower_designs.dm
@@ -8,7 +8,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/synthflesh = 25)
build_path = /obj/item/bodypart/l_arm
- category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph")
+ category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph","vox")
/datum/design/rightarm
name = "Right Arm"
@@ -16,7 +16,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/synthflesh = 25)
build_path = /obj/item/bodypart/r_arm
- category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph")
+ category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph","vox")
/datum/design/leftleg
name = "Left Leg"
@@ -24,7 +24,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/synthflesh = 25)
build_path = /obj/item/bodypart/l_leg
- category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph")
+ category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph","vox")
/datum/design/rightleg
name = "Right Leg"
@@ -32,7 +32,7 @@
build_type = LIMBGROWER
reagents_list = list(/datum/reagent/medicine/synthflesh = 25)
build_path = /obj/item/bodypart/r_leg
- category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph")
+ category = list("initial","human","lizard","fly","moth","plasmaman","polysmorph","vox")
/datum/design/digi_leftleg
name = "Digitigrade Left Leg"
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 326707ee031cc..85cd95a2eb983 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -78,7 +78,7 @@
display_name = "Xeno-organ Biology"
description = "Plasmaman, Ethereals, Lizardpeople... What makes our non-human crewmembers tick?"
prereq_ids = list("adv_biotech")
- design_ids = list("limbdesign_felinid", "limbdesign_lizard", "limbdesign_plasmaman", "limbdesign_ethereal", "limbdesign_polysmorph")
+ design_ids = list("limbdesign_felinid", "limbdesign_lizard", "limbdesign_plasmaman", "limbdesign_ethereal", "limbdesign_polysmorph", "limbdesign_vox")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/bio_process
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 579543f086c64..17325de1343ac 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -52,6 +52,7 @@
var/skin_tone = ""
var/body_gender = ""
var/species_id = ""
+ var/is_husked = FALSE
var/should_draw_gender = FALSE
var/should_draw_greyscale = FALSE
var/species_color = ""
@@ -824,16 +825,23 @@
no_update = TRUE
else
no_update = FALSE
-
+ is_husked = FALSE
if(HAS_TRAIT(C, TRAIT_HUSK) && is_organic_limb())
if(ishuman(C))
var/mob/living/carbon/human/S = C
if(isszlachta(S))
return
- species_id = "husk" //overrides species_id
+ var/datum/species/id_to_species
+ var/species_type = GLOB.species_list[species_id]
+ if(species_type)
+ id_to_species = new species_type()
+ if(id_to_species && !id_to_species.generate_husk_icon)
+ species_id = "husk" //overrides species_id
+
dmg_overlay_type = "" //no damage overlay shown when husked
should_draw_gender = FALSE
should_draw_greyscale = FALSE
+ is_husked = TRUE
no_update = TRUE
if(no_update)
@@ -846,6 +854,14 @@
var/datum/species/S = H.dna.species
if(!limb_override)
species_id = S.limbs_id
+ var/datum/species/id_to_species
+ var/species_type = GLOB.species_list[species_id]
+ if(species_type)
+ id_to_species = new species_type()
+ if(id_to_species && (body_zone in id_to_species.static_part_body_zones))
+ has_static_sprite_part = TRUE
+ else
+ has_static_sprite_part = FALSE
species_flags_list = S.species_traits
if(S.use_skintones)
@@ -859,10 +875,13 @@
body_gender = H.gender
- should_draw_gender = (FEMALE in S.possible_genders)
+ if(S.is_dimorphic)
+ should_draw_gender = (FEMALE in S.possible_genders)
+ limb_icon_variant = S.get_icon_variant(H)
+ limb_icon_file = S.limb_icon_file
use_damage_color = S.use_damage_color
- if((MUTCOLORS in S.species_traits) || (DYNCOLORS in S.species_traits))
+ if((MUTCOLORS in S.species_traits) && !limb_icon_variant || (DYNCOLORS in S.species_traits))
if(S.fixed_mut_color)
species_color = S.fixed_mut_color
else
@@ -918,6 +937,7 @@
. += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER, image_dir)
var/image/limb = image(layer = -BODYPARTS_LAYER, dir = image_dir)
+ var/image/limb_static
var/image/aux
. += limb
@@ -934,8 +954,11 @@
return
var/icon_gender = (body_gender == FEMALE) ? "f" : "m" //gender of the icon, if applicable
-
- if((body_zone != BODY_ZONE_HEAD && body_zone != BODY_ZONE_CHEST))
+ var/datum/species/id_to_species
+ var/species_type = GLOB.species_list[species_id]
+ if(species_type)
+ id_to_species = new species_type()
+ if(((body_zone != BODY_ZONE_HEAD && body_zone != BODY_ZONE_CHEST )) || (id_to_species && !id_to_species.is_dimorphic))
should_draw_gender = FALSE
if(status == BODYPART_ORGANIC || (status == BODYPART_ROBOTIC && render_like_organic == TRUE)) // So IPC augments can be colorful without disrupting normal BODYPART_ROBOTIC render code.
@@ -953,15 +976,25 @@
else
limb.icon_state = "[species_id]_[body_zone]"
else
- limb.icon = 'yogstation/icons/mob/human_parts.dmi' // yogs -- use yogs icon instead of tg, gorilla people
+ limb.icon = limb_icon_file || 'yogstation/icons/mob/human_parts.dmi' // yogs -- use yogs icon instead of tg, gorilla people
if(should_draw_gender)
limb.icon_state = "[species_id]_[body_zone]_[icon_gender]"
else
limb.icon_state = "[species_id]_[body_zone]"
+ if(limb_icon_variant)
+ limb.icon_state += "_[limb_icon_variant]"
if(aux_zone)
- aux = image(limb.icon, "[species_id]_[aux_zone]", -aux_layer, image_dir)
+ var/aux_icon_name = "[species_id]_[aux_zone]_[limb_icon_variant]"
+ if(!icon_exists(limb.icon, "[aux_icon_name]"))
+ aux_icon_name = "[species_id]_[aux_zone]"
+ aux = image(limb.icon, aux_icon_name, -aux_layer, image_dir)
. += aux
-
+ if(has_static_sprite_part)
+ var/limb_static_icon_name = "[species_id]_[body_zone]_static"
+ if(id_to_species && (limb_icon_variant in id_to_species.get_special_statics()))
+ limb_static_icon_name += "_[limb_icon_variant]"
+ limb_static = image(limb.icon, limb_static_icon_name, limb.layer, limb.dir)
+ . += limb_static
else
limb.icon = icon
if(should_draw_gender)
@@ -977,13 +1010,34 @@
. += aux
return
-
+ var/draw_color
if(should_draw_greyscale)
- var/draw_color = mutation_color || species_color || (skin_tone && skintone2hex(skin_tone))
- if(draw_color)
- limb.color = "[draw_color]"
- if(aux_zone)
- aux.color = "[draw_color]"
+ draw_color = mutation_color || species_color || (skin_tone && skintone2hex(skin_tone))
+
+ if(is_husked)
+ huskify_image(limb, owner, TRUE, id_to_species)
+ if(aux)
+ huskify_image(aux, owner, TRUE, id_to_species)
+ if(limb_static)
+ huskify_image(limb_static, owner, TRUE, id_to_species)
+ draw_color = id_to_species?.husk_color || owner?.dna?.species?.husk_color
+
+ if(draw_color)
+ limb.color = "[draw_color]"
+ if(aux_zone)
+ aux.color = "[draw_color]"
+
+/proc/huskify_image(image/thing_to_husk, mob/living/carbon/husked_guy, draw_blood = TRUE, datum/species/passed_species)
+ var/husk_color_mod = rgb(96, 88, 80)
+ var/icon/husk_icon = new(thing_to_husk.icon)
+ husk_icon.ColorTone(husk_color_mod)
+ thing_to_husk.icon = husk_icon
+ var/icon_of_husk = husked_guy?.dna?.species?.icon_husk || passed_species?.icon_husk
+ if(draw_blood)
+ var/mutable_appearance/husk_blood = mutable_appearance(icon_of_husk || 'yogstation/icons/mob/human_parts.dmi', "overlay_[husked_guy?.dna?.species?.id || passed_species?.id]husk", appearance_flags = RESET_COLOR)
+ husk_blood.blend_mode = BLEND_INSET_OVERLAY
+ husk_blood.dir = thing_to_husk.dir
+ thing_to_husk.add_overlay(husk_blood)
/obj/item/bodypart/deconstruct(disassembled = TRUE)
drop_organs()
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index a4de79fd173ba..af39d0dfa9e6f 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -22,7 +22,7 @@
var/obj/item/organ/eyes/eyes
var/obj/item/organ/ears/ears
var/obj/item/organ/tongue/tongue
-
+ var/eyes_icon = 'icons/mob/human_face.dmi'
//Limb appearance info:
var/real_name = "" //Replacement name
//Hair colour and style
@@ -190,6 +190,10 @@
else
hair_color = "000"
hair_alpha = initial(hair_alpha)
+ if(HAIRCOLOR in S.species_traits)
+ hair_color = H.hair_color
+ if(FACEHAIRCOLOR in S.species_traits)
+ facial_hair_color = H.facial_hair_color
// lipstick
if(H.lip_style && (LIPS in S.species_traits))
lip_style = H.lip_style
@@ -197,6 +201,9 @@
else
lip_style = null
lip_color = "white"
+ if(S.eyes_icon)
+ eyes_icon = S.eyes_icon
+ eyes_static = S.get_eyes_static(H)
..()
/obj/item/bodypart/head/update_icon_dropped()
@@ -253,13 +260,17 @@
. += lips_overlay
// eyes
- var/image/eyes_overlay = image('icons/mob/human_face.dmi', "eyes_missing", -BODY_LAYER, SOUTH)
+ var/image/eyes_overlay = image(eyes_icon, "eyes_missing", -BODY_LAYER, SOUTH)
. += eyes_overlay
if(eyes)
eyes_overlay.icon_state = eyes.eye_icon_state
-
if(eyes.eye_color)
eyes_overlay.color = eyes.eye_color
+ if(eyes_static)
+ var/mutable_appearance/eyes_static_sprite = mutable_appearance(eyes_overlay.icon, "[eyes_overlay.icon_state]_static_[eyes_static]", eyes_overlay.layer)
+ eyes_static_sprite.dir = eyes_overlay.dir
+ eyes_static_sprite.appearance_flags |= RESET_COLOR
+ eyes_overlay.add_overlay(eyes_static_sprite)
/obj/item/bodypart/head/monkey
icon = 'icons/mob/animal_parts.dmi'
diff --git a/code/modules/surgery/organs/appendix.dm b/code/modules/surgery/organs/appendix.dm
index e45511648e94d..8c0baea181ce7 100644
--- a/code/modules/surgery/organs/appendix.dm
+++ b/code/modules/surgery/organs/appendix.dm
@@ -13,16 +13,16 @@
/obj/item/organ/appendix/update_name(updates=ALL)
. = ..()
if(inflamed)
- name = "inflamed appendix"
+ name = "inflamed [initial(name)]"
else
- name = "appendix"
+ name = initial(name)
/obj/item/organ/appendix/update_icon_state()
. = ..()
if(inflamed)
- icon_state = "appendixinflamed"
+ icon_state = "[initial(icon_state)]inflamed"
else
- icon_state = "appendix"
+ icon_state = initial(icon_state)
/obj/item/organ/appendix/on_life()
..()
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 468ccea83bf29..7c8af8cdc31a4 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -94,16 +94,20 @@
/obj/item/organ/eyes/proc/generate_body_overlay(mob/living/carbon/human/parent)
if(!istype(parent) || parent.getorgan(/obj/item/organ/eyes) != src)
CRASH("Generating a body overlay for [src] targeting an invalid parent '[parent]'.")
-
- var/mutable_appearance/eye_overlay = mutable_appearance('icons/mob/human_face.dmi', eye_icon_state, -BODY_LAYER)
+ var/obj/item/bodypart/head/head = parent.get_bodypart(BODY_ZONE_HEAD)
+ var/mutable_appearance/eye_overlay = mutable_appearance(head.eyes_icon, eye_icon_state, -BODY_LAYER)
var/list/overlays = list(eye_overlay)
if((EYECOLOR in parent.dna.species.species_traits))
eye_overlay.color = eye_color
+ if(head.eyes_static)
+ var/mutable_appearance/eyes_static_sprite = mutable_appearance(eye_overlay.icon, "[eye_overlay.icon_state]_static_[head.eyes_static]", eye_overlay.layer, appearance_flags = RESET_COLOR)
+ eye_overlay.add_overlay(eyes_static_sprite)
+
// Cry emote overlay
- if (HAS_TRAIT(parent, TRAIT_CRYING)) // Caused by the *cry emote
- var/mutable_appearance/tears_overlay = mutable_appearance('icons/mob/human_face.dmi', "tears", -BODY_ADJ_LAYER)
+ if(HAS_TRAIT(parent, TRAIT_CRYING)) // Caused by the *cry emote
+ var/mutable_appearance/tears_overlay = mutable_appearance(head.eyes_icon, "tears", -BODY_ADJ_LAYER)
tears_overlay.color = COLOR_DARK_CYAN
overlays += tears_overlay
diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm
index 35d93c8863dea..526b2cb6046fe 100644
--- a/code/modules/surgery/organs/lungs.dm
+++ b/code/modules/surgery/organs/lungs.dm
@@ -117,6 +117,17 @@
alert_type = alert["alert_type"]
if(alert_category)
H.throw_alert(alert_category, alert_type)
+ var/list/too_much_gas_alerts = list()
+ for(var/gas in gas_max)
+ var/gas_alert_category
+ if(ispath(gas))
+ var/datum/breathing_class/breathclass = gas
+ gas_alert_category = breathclass.high_alert_category
+ else
+ gas_alert_category = GLOB.gas_data.breath_alert_info[gas]["too_much_alert"]["alert_category"]
+ too_much_gas_alerts += gas_alert_category
+ for(var/alert as anything in too_much_gas_alerts)
+ H.clear_alert(alert)
return FALSE
#define PP_MOLES(X) ((X / total_moles) * pressure)
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 92b50110451d1..3db5fb0b582af 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -29,6 +29,7 @@
/datum/language/japanese,
/datum/language/machine, //yogs
/datum/language/darkspawn, //also yogs
+ /datum/language/vox,
/datum/language/encrypted,
/datum/language/felinid,
/datum/language/english,
diff --git a/config/game_options.txt b/config/game_options.txt
index 3d919ac808919..e8d5a9a5c87b6 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -19,10 +19,10 @@ REVIVAL_BRAIN_LIFE -1
JOB_SPECIES_WHITELIST /datum/job/captain human
JOB_SPECIES_WHITELIST /datum/job/hop human,lizard,plasmaman,ipc
-JOB_SPECIES_WHITELIST /datum/job/hos human,lizard,pod,preternis,polysmorph
-JOB_SPECIES_WHITELIST /datum/job/chief_engineer human,lizard,pod,plasmaman,moth,ethereal,preternis,polysmorph,ipc
-JOB_SPECIES_WHITELIST /datum/job/rd human,lizard,pod,plasmaman,moth,ethereal,preternis,polysmorph,ipc
-JOB_SPECIES_WHITELIST /datum/job/cmo human,lizard,pod,plasmaman,moth,ethereal,preternis,polysmorph,ipc
+JOB_SPECIES_WHITELIST /datum/job/hos human,lizard,pod,preternis,polysmorph,vox
+JOB_SPECIES_WHITELIST /datum/job/chief_engineer human,lizard,pod,plasmaman,moth,ethereal,preternis,polysmorph,ipc,vox
+JOB_SPECIES_WHITELIST /datum/job/rd human,lizard,pod,plasmaman,moth,ethereal,preternis,polysmorph,ipc,vox
+JOB_SPECIES_WHITELIST /datum/job/cmo human,lizard,pod,plasmaman,moth,ethereal,preternis,polysmorph,ipc,vox
## OOC DURING ROUND ###
@@ -562,6 +562,7 @@ ROUNDSTART_RACES preternis
ROUNDSTART_RACES polysmorph
#ROUNDSTART_RACES snail
ROUNDSTART_RACES ipc
+ROUNDSTART_RACES vox
## Races that are better than humans in some ways, but worse in others
ROUNDSTART_RACES ethereal
diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi
index 483ab04442334..63b8a39a11900 100644
Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ
diff --git a/icons/effects/crayondecal.dmi b/icons/effects/crayondecal.dmi
index c9e7f880c8092..d1f9461a20433 100755
Binary files a/icons/effects/crayondecal.dmi and b/icons/effects/crayondecal.dmi differ
diff --git a/icons/effects/creampie.dmi b/icons/effects/creampie.dmi
index 6caecf7d767c3..bdb39e5698ff7 100644
Binary files a/icons/effects/creampie.dmi and b/icons/effects/creampie.dmi differ
diff --git a/icons/misc/language.dmi b/icons/misc/language.dmi
index 86e8a0aeacb41..188d9b834227e 100644
Binary files a/icons/misc/language.dmi and b/icons/misc/language.dmi differ
diff --git a/icons/mob/butts.dmi b/icons/mob/butts.dmi
index ae4b41961a1cd..fb14886ba10a4 100644
Binary files a/icons/mob/butts.dmi and b/icons/mob/butts.dmi differ
diff --git a/icons/mob/clothing/feet/feet.dmi b/icons/mob/clothing/feet/feet.dmi
index b6a4e7dc80fde..44cd673724a93 100644
Binary files a/icons/mob/clothing/feet/feet.dmi and b/icons/mob/clothing/feet/feet.dmi differ
diff --git a/icons/mob/clothing/head/head.dmi b/icons/mob/clothing/head/head.dmi
index 40c4208387301..6710eb3d925f8 100644
Binary files a/icons/mob/clothing/head/head.dmi and b/icons/mob/clothing/head/head.dmi differ
diff --git a/icons/mob/clothing/species/vox/back.dmi b/icons/mob/clothing/species/vox/back.dmi
new file mode 100644
index 0000000000000..9288778584d1a
Binary files /dev/null and b/icons/mob/clothing/species/vox/back.dmi differ
diff --git a/icons/mob/clothing/species/vox/collar.dmi b/icons/mob/clothing/species/vox/collar.dmi
new file mode 100644
index 0000000000000..6c442c62f7751
Binary files /dev/null and b/icons/mob/clothing/species/vox/collar.dmi differ
diff --git a/icons/mob/clothing/species/vox/ears.dmi b/icons/mob/clothing/species/vox/ears.dmi
new file mode 100644
index 0000000000000..cd43d1a1893a7
Binary files /dev/null and b/icons/mob/clothing/species/vox/ears.dmi differ
diff --git a/icons/mob/clothing/species/vox/eyes.dmi b/icons/mob/clothing/species/vox/eyes.dmi
new file mode 100644
index 0000000000000..5962060f0d23e
Binary files /dev/null and b/icons/mob/clothing/species/vox/eyes.dmi differ
diff --git a/icons/mob/clothing/species/vox/gloves.dmi b/icons/mob/clothing/species/vox/gloves.dmi
new file mode 100644
index 0000000000000..4ac7a044f2f02
Binary files /dev/null and b/icons/mob/clothing/species/vox/gloves.dmi differ
diff --git a/icons/mob/clothing/species/vox/head.dmi b/icons/mob/clothing/species/vox/head.dmi
new file mode 100644
index 0000000000000..7e4d425a948e0
Binary files /dev/null and b/icons/mob/clothing/species/vox/head.dmi differ
diff --git a/icons/mob/clothing/species/vox/held.dmi b/icons/mob/clothing/species/vox/held.dmi
new file mode 100644
index 0000000000000..973f8aa94cccb
Binary files /dev/null and b/icons/mob/clothing/species/vox/held.dmi differ
diff --git a/icons/mob/clothing/species/vox/helmet.dmi b/icons/mob/clothing/species/vox/helmet.dmi
new file mode 100644
index 0000000000000..a36767b34a282
Binary files /dev/null and b/icons/mob/clothing/species/vox/helmet.dmi differ
diff --git a/icons/mob/clothing/species/vox/mask.dmi b/icons/mob/clothing/species/vox/mask.dmi
new file mode 100644
index 0000000000000..4653947806427
Binary files /dev/null and b/icons/mob/clothing/species/vox/mask.dmi differ
diff --git a/icons/mob/clothing/species/vox/shoes.dmi b/icons/mob/clothing/species/vox/shoes.dmi
new file mode 100644
index 0000000000000..eba8a11aa2ee9
Binary files /dev/null and b/icons/mob/clothing/species/vox/shoes.dmi differ
diff --git a/icons/mob/clothing/species/vox/socks.dmi b/icons/mob/clothing/species/vox/socks.dmi
new file mode 100644
index 0000000000000..e9e433a82db6b
Binary files /dev/null and b/icons/mob/clothing/species/vox/socks.dmi differ
diff --git a/icons/mob/clothing/species/vox/suit.dmi b/icons/mob/clothing/species/vox/suit.dmi
new file mode 100644
index 0000000000000..ccf234e740ea9
Binary files /dev/null and b/icons/mob/clothing/species/vox/suit.dmi differ
diff --git a/icons/mob/clothing/species/vox/undershirt.dmi b/icons/mob/clothing/species/vox/undershirt.dmi
new file mode 100644
index 0000000000000..b43f2ed8cd124
Binary files /dev/null and b/icons/mob/clothing/species/vox/undershirt.dmi differ
diff --git a/icons/mob/clothing/species/vox/underwear.dmi b/icons/mob/clothing/species/vox/underwear.dmi
new file mode 100644
index 0000000000000..81fb752d12e27
Binary files /dev/null and b/icons/mob/clothing/species/vox/underwear.dmi differ
diff --git a/icons/mob/clothing/species/vox/uniform.dmi b/icons/mob/clothing/species/vox/uniform.dmi
new file mode 100644
index 0000000000000..cd8bb9bee358d
Binary files /dev/null and b/icons/mob/clothing/species/vox/uniform.dmi differ
diff --git a/icons/mob/clothing/suit/suit.dmi b/icons/mob/clothing/suit/suit.dmi
index e796e64a1e664..a41781c796743 100644
Binary files a/icons/mob/clothing/suit/suit.dmi and b/icons/mob/clothing/suit/suit.dmi differ
diff --git a/icons/mob/clothing/uniform/color.dmi b/icons/mob/clothing/uniform/color.dmi
index 9b06725cbba7e..4e63fcd41719f 100644
Binary files a/icons/mob/clothing/uniform/color.dmi and b/icons/mob/clothing/uniform/color.dmi differ
diff --git a/icons/mob/corgi_head.dmi b/icons/mob/corgi_head.dmi
index a525a36c2d461..7c610b8007761 100644
Binary files a/icons/mob/corgi_head.dmi and b/icons/mob/corgi_head.dmi differ
diff --git a/icons/mob/human.dmi b/icons/mob/human.dmi
index 63284a0012f27..2a5ee78d27cb3 100644
Binary files a/icons/mob/human.dmi and b/icons/mob/human.dmi differ
diff --git a/icons/mob/human_face.dmi b/icons/mob/human_face.dmi
index dd22f1dc5bc42..dcc27abd3c09b 100644
Binary files a/icons/mob/human_face.dmi and b/icons/mob/human_face.dmi differ
diff --git a/icons/mob/inhands/equipment/tanks_lefthand.dmi b/icons/mob/inhands/equipment/tanks_lefthand.dmi
index 47f9e4be647b2..b07ed1e5d5307 100644
Binary files a/icons/mob/inhands/equipment/tanks_lefthand.dmi and b/icons/mob/inhands/equipment/tanks_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/tanks_righthand.dmi b/icons/mob/inhands/equipment/tanks_righthand.dmi
index 2de83e274a256..84ffef493adee 100644
Binary files a/icons/mob/inhands/equipment/tanks_righthand.dmi and b/icons/mob/inhands/equipment/tanks_righthand.dmi differ
diff --git a/icons/mob/inhands/flags_lefthand.dmi b/icons/mob/inhands/flags_lefthand.dmi
new file mode 100644
index 0000000000000..6b5c936dcc8d6
Binary files /dev/null and b/icons/mob/inhands/flags_lefthand.dmi differ
diff --git a/icons/mob/inhands/flags_righthand.dmi b/icons/mob/inhands/flags_righthand.dmi
new file mode 100644
index 0000000000000..85088c7a8df59
Binary files /dev/null and b/icons/mob/inhands/flags_righthand.dmi differ
diff --git a/icons/mob/inhands/misc/food_lefthand.dmi b/icons/mob/inhands/misc/food_lefthand.dmi
index c58a87284eb39..02281580dd52d 100644
Binary files a/icons/mob/inhands/misc/food_lefthand.dmi and b/icons/mob/inhands/misc/food_lefthand.dmi differ
diff --git a/icons/mob/inhands/misc/food_righthand.dmi b/icons/mob/inhands/misc/food_righthand.dmi
index 186dc8d55537e..a7690f04eed83 100644
Binary files a/icons/mob/inhands/misc/food_righthand.dmi and b/icons/mob/inhands/misc/food_righthand.dmi differ
diff --git a/icons/mob/species/vox/body_markings.dmi b/icons/mob/species/vox/body_markings.dmi
new file mode 100644
index 0000000000000..0a6b23ed2e1e4
Binary files /dev/null and b/icons/mob/species/vox/body_markings.dmi differ
diff --git a/icons/mob/species/vox/bodyparts.dmi b/icons/mob/species/vox/bodyparts.dmi
new file mode 100644
index 0000000000000..99a579d462cf7
Binary files /dev/null and b/icons/mob/species/vox/bodyparts.dmi differ
diff --git a/icons/mob/species/vox/eyes.dmi b/icons/mob/species/vox/eyes.dmi
new file mode 100644
index 0000000000000..64ec6f77ec629
Binary files /dev/null and b/icons/mob/species/vox/eyes.dmi differ
diff --git a/icons/mob/species/vox/facial_quills.dmi b/icons/mob/species/vox/facial_quills.dmi
new file mode 100644
index 0000000000000..b4a79df754ba0
Binary files /dev/null and b/icons/mob/species/vox/facial_quills.dmi differ
diff --git a/icons/mob/species/vox/quills.dmi b/icons/mob/species/vox/quills.dmi
new file mode 100644
index 0000000000000..da5af684da145
Binary files /dev/null and b/icons/mob/species/vox/quills.dmi differ
diff --git a/icons/mob/species/vox/tail_markings.dmi b/icons/mob/species/vox/tail_markings.dmi
new file mode 100644
index 0000000000000..69f372ddcb9ed
Binary files /dev/null and b/icons/mob/species/vox/tail_markings.dmi differ
diff --git a/icons/mob/species/vox/tails.dmi b/icons/mob/species/vox/tails.dmi
new file mode 100644
index 0000000000000..ae23a1fae9ae0
Binary files /dev/null and b/icons/mob/species/vox/tails.dmi differ
diff --git a/icons/obj/assemblies.dmi b/icons/obj/assemblies.dmi
index 25d4a2a9cac04..c66dba6228275 100644
Binary files a/icons/obj/assemblies.dmi and b/icons/obj/assemblies.dmi differ
diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi
index 01d73a179eec4..e5bb9f997b55a 100644
Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ
diff --git a/icons/obj/clothing/species/vox/gloves.dmi b/icons/obj/clothing/species/vox/gloves.dmi
new file mode 100644
index 0000000000000..eeca4b63f30b2
Binary files /dev/null and b/icons/obj/clothing/species/vox/gloves.dmi differ
diff --git a/icons/obj/clothing/species/vox/hats.dmi b/icons/obj/clothing/species/vox/hats.dmi
new file mode 100644
index 0000000000000..922ede57f1afd
Binary files /dev/null and b/icons/obj/clothing/species/vox/hats.dmi differ
diff --git a/icons/obj/clothing/species/vox/shoes.dmi b/icons/obj/clothing/species/vox/shoes.dmi
new file mode 100644
index 0000000000000..a1dde383ff727
Binary files /dev/null and b/icons/obj/clothing/species/vox/shoes.dmi differ
diff --git a/icons/obj/clothing/species/vox/suits.dmi b/icons/obj/clothing/species/vox/suits.dmi
new file mode 100644
index 0000000000000..4dc0ade4fb89c
Binary files /dev/null and b/icons/obj/clothing/species/vox/suits.dmi differ
diff --git a/icons/obj/clothing/species/vox/uniforms.dmi b/icons/obj/clothing/species/vox/uniforms.dmi
new file mode 100644
index 0000000000000..4bf1b92308d09
Binary files /dev/null and b/icons/obj/clothing/species/vox/uniforms.dmi differ
diff --git a/icons/obj/clothing/suits/suits.dmi b/icons/obj/clothing/suits/suits.dmi
index bfe2f6a535d3d..e646f0eb2016d 100644
Binary files a/icons/obj/clothing/suits/suits.dmi and b/icons/obj/clothing/suits/suits.dmi differ
diff --git a/icons/obj/decals.dmi b/icons/obj/decals.dmi
index 8ed868f984c64..8f069529592ba 100644
Binary files a/icons/obj/decals.dmi and b/icons/obj/decals.dmi differ
diff --git a/icons/obj/flag.dmi b/icons/obj/flag.dmi
new file mode 100644
index 0000000000000..a311c47f8840d
Binary files /dev/null and b/icons/obj/flag.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 310033272db2d..495105e65e45c 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/plushes.dmi b/icons/obj/plushes.dmi
index 82665c089b15a..a66158ffde33e 100644
Binary files a/icons/obj/plushes.dmi and b/icons/obj/plushes.dmi differ
diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi
index ce2743612a4da..d88ae0e3504d3 100644
Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index a728c79817a94..e779a3f2e0b76 100755
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/sound/effects/voxrustle.ogg b/sound/effects/voxrustle.ogg
new file mode 100644
index 0000000000000..fe400fc5dd181
Binary files /dev/null and b/sound/effects/voxrustle.ogg differ
diff --git a/sound/voice/vox/ashriek.ogg b/sound/voice/vox/ashriek.ogg
new file mode 100644
index 0000000000000..125ba868fdbf4
Binary files /dev/null and b/sound/voice/vox/ashriek.ogg differ
diff --git a/sound/voice/vox/shriek1.ogg b/sound/voice/vox/shriek1.ogg
new file mode 100644
index 0000000000000..bb13db1ca0ee6
Binary files /dev/null and b/sound/voice/vox/shriek1.ogg differ
diff --git a/sound/voice/vox/shriekcough.ogg b/sound/voice/vox/shriekcough.ogg
new file mode 100644
index 0000000000000..15dc2e3873cbd
Binary files /dev/null and b/sound/voice/vox/shriekcough.ogg differ
diff --git a/sound/voice/vox/shrieksneeze.ogg b/sound/voice/vox/shrieksneeze.ogg
new file mode 100644
index 0000000000000..9b1a5d1cca735
Binary files /dev/null and b/sound/voice/vox/shrieksneeze.ogg differ
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
index e25bec52472c3..f4a6ae3e5fbe9 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx
@@ -78,7 +78,7 @@ export const CharacterPreferenceWindow = (props, context) => {
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx
index e53f1b3ee5c85..9fd6b472df2e2 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/MainPage.tsx
@@ -341,7 +341,11 @@ const createSetRandomization = (
const sortPreferences = sortBy<[string, unknown]>(
([featureId, _]) => {
const feature = features[featureId];
- return feature?.name;
+ if (feature?.sortingPrefix) {
+ return feature.sortingPrefix + feature.name;
+ } else {
+ return feature?.name;
+ }
});
const PreferenceList = (props: {
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx
index 0800d809c5fd8..75487daff1794 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/base.tsx
@@ -22,6 +22,7 @@ export type Feature<
>;
category?: string;
description?: string;
+ sortingPrefix?: string;
};
/**
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skin_tone.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skin_tone.tsx
index 3ee93eb571bd3..e6b47438e0362 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skin_tone.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skin_tone.tsx
@@ -2,17 +2,17 @@ import { sortBy } from "common/collections";
import { Box, Stack } from "../../../../../components";
import { Feature, FeatureChoicedServerData, FeatureValueProps, StandardizedDropdown } from "../base";
-type HexValue = {
+export type HexValue = {
lightness: number,
value: string,
};
-type SkinToneServerData = FeatureChoicedServerData & {
+export type SkinToneServerData = FeatureChoicedServerData & {
display_names: NonNullable,
to_hex: Record,
};
-const sortHexValues
+export const sortHexValues
= sortBy<[string, HexValue]>(([_, hexValue]) => -hexValue.lightness);
export const skin_tone: Feature = {
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx
index 51171da675986..8a8fc54c19872 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx
@@ -1,7 +1,11 @@
-import { FeatureColorInput, Feature, FeatureChoiced, FeatureDropdownInput } from "./base";
+import { Box, Stack } from "../../../../components";
+import { FeatureColorInput, FeatureChoicedServerData, FeatureValueProps, StandardizedDropdown, Feature, FeatureChoiced, FeatureDropdownInput } from "./base";
+import { SkinToneServerData } from "./character_preferences/skin_tone";
+import { sortHexValues } from "./character_preferences/skin_tone";
export const eye_color: Feature = {
- name: "Eye color",
+ name: "Eye Color",
+ sortingPrefix: "aaaa",
component: FeatureColorInput,
};
@@ -50,6 +54,45 @@ export const feature_mcolor: Feature = {
component: FeatureColorInput,
};
+export const feature_mcolor_secondary: Feature = {
+ name: "Secondary Mutant color",
+ component: FeatureColorInput,
+};
+
+export const feature_quill_color: Feature = {
+ name: "Quill Color",
+ component: FeatureColorInput,
+};
+
+export const feature_facial_quill_color: Feature = {
+ name: "Facial Quill Color",
+ component: FeatureColorInput,
+};
+
+export const feature_quill_gradientstyle: FeatureChoiced = {
+ name: "Quill Gradient",
+ sortingPrefix: "v1",
+ component: FeatureDropdownInput,
+};
+
+export const feature_quill_gradientcolor: Feature = {
+ name: "Quill Gradient Color",
+ sortingPrefix: "v2",
+ component: FeatureColorInput,
+};
+
+export const feature_body_markings_color: Feature = {
+ name: "Body Markings Color",
+ sortingPrefix: "v6",
+ component: FeatureColorInput,
+};
+
+export const feature_tail_markings_color: Feature = {
+ name: "Tail Markings Color",
+ sortingPrefix: "v4",
+ component: FeatureColorInput,
+};
+
export const feature_ipc_screen: FeatureChoiced = {
name: "Screen",
component: FeatureDropdownInput,
@@ -134,3 +177,100 @@ export const feature_preternis_eye: FeatureChoiced = {
name: "Eye",
component: FeatureDropdownInput,
};
+
+export const feature_vox_quills: FeatureChoiced = {
+ name: 'Quillstyle',
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_facial_quills: FeatureChoiced = {
+ name: 'Facial Quillstyle',
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_tail_markings: FeatureChoiced = {
+ name: 'Tail Markings',
+ sortingPrefix: "v3",
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_body_markings: FeatureChoiced = {
+ name: 'Body Markings',
+ sortingPrefix: "v5",
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_skin_tone: Feature = {
+ name: "Skin Tone",
+ sortingPrefix: "a",
+ component: (props: FeatureValueProps) => {
+ const {
+ handleSetValue,
+ serverData,
+ value,
+ } = props;
+
+ if (!serverData) {
+ return null;
+ }
+
+ return (
+ key)}
+ displayNames={Object.fromEntries(
+ Object.entries(serverData.display_names)
+ .map(([key, displayName]) => {
+ const hexColor = serverData.to_hex[key];
+
+ return [key, (
+
+
+
+
+
+
+ {displayName}
+
+
+ )];
+ })
+ )}
+ onSetValue={handleSetValue}
+ value={value}
+ />
+ );
+ },
+};
+
+export const feature_vox_underwear: FeatureChoiced = {
+ name: 'Underwear',
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_socks: FeatureChoiced = {
+ name: 'Socks',
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_undershirt: FeatureChoiced = {
+ name: 'Undershirt',
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_tank_type: FeatureChoiced = {
+ name: 'N² Tank',
+ component: FeatureDropdownInput,
+};
+
+export const feature_vox_mask: FeatureChoiced = {
+ name: 'Mask',
+ component: FeatureDropdownInput,
+};
diff --git a/yogstation.dme b/yogstation.dme
index 248ebfefdb6de..41d59cdfd67cc 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -143,6 +143,7 @@
#include "code\__DEFINES\space.dm"
#include "code\__DEFINES\spaceman_dmm.dm"
#include "code\__DEFINES\span.dm"
+#include "code\__DEFINES\species_clothing_paths.dm"
#include "code\__DEFINES\speech_channels.dm"
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
@@ -226,6 +227,7 @@
#include "code\__DEFINES\{yogs_defines}\admin.dm"
#include "code\__DEFINES\{yogs_defines}\antagonists.dm"
#include "code\__DEFINES\{yogs_defines}\atmospherics.dm"
+#include "code\__DEFINES\{yogs_defines}\colors.dm"
#include "code\__DEFINES\{yogs_defines}\components.dm"
#include "code\__DEFINES\{yogs_defines}\darkspawn.dm"
#include "code\__DEFINES\{yogs_defines}\DNA.dm"
@@ -244,6 +246,7 @@
#include "code\__DEFINES\{yogs_defines}\reactions.dm"
#include "code\__DEFINES\{yogs_defines}\shuttles.dm"
#include "code\__DEFINES\{yogs_defines}\spacepods.dm"
+#include "code\__DEFINES\{yogs_defines}\species_clothing_paths.dm"
#include "code\__DEFINES\{yogs_defines}\status_effects.dm"
#include "code\__DEFINES\{yogs_defines}\telecomms.dm"
#include "code\__DEFINES\{yogs_defines}\traits.dm"
@@ -4013,6 +4016,7 @@
#include "yogstation\code\__HELPERS\unsorted.dm"
#include "yogstation\code\_globalvars\configuration.dm"
#include "yogstation\code\_globalvars\logging.dm"
+#include "yogstation\code\_globalvars\lists\flavor_misc.dm"
#include "yogstation\code\_globalvars\lists\game.dm"
#include "yogstation\code\_globalvars\lists\mentors.dm"
#include "yogstation\code\_globalvars\lists\names.dm"
@@ -4029,6 +4033,9 @@
#include "yogstation\code\controllers\subsystem\yogs.dm"
#include "yogstation\code\controllers\subsystem\processing\quirks.dm"
#include "yogstation\code\datums\action.dm"
+#include "yogstation\code\datums\blood_types.dm"
+#include "yogstation\code\datums\dog_fashion.dm"
+#include "yogstation\code\datums\emotes.dm"
#include "yogstation\code\datums\mind.dm"
#include "yogstation\code\datums\mutations.dm"
#include "yogstation\code\datums\shuttles.dm"
@@ -4067,6 +4074,7 @@
#include "yogstation\code\datums\ruins\station.dm"
#include "yogstation\code\datums\status_effects\buffs.dm"
#include "yogstation\code\datums\status_effects\neutral.dm"
+#include "yogstation\code\datums\traits\good.dm"
#include "yogstation\code\datums\wires\disposals.dm"
#include "yogstation\code\datums\wires\smartfridge.dm"
#include "yogstation\code\game\communications.dm"
@@ -4122,6 +4130,8 @@
#include "yogstation\code\game\mecha\mecha_wreckage.dm"
#include "yogstation\code\game\mecha\makeshift\lockermech.dm"
#include "yogstation\code\game\mecha\makeshift\makeshift_tools.dm"
+#include "yogstation\code\game\objects\items.dm"
+#include "yogstation\code\game\objects\objs.dm"
#include "yogstation\code\game\objects\effects\contraband.dm"
#include "yogstation\code\game\objects\effects\countdown.dm"
#include "yogstation\code\game\objects\effects\landmarks.dm"
@@ -4140,6 +4150,7 @@
#include "yogstation\code\game\objects\items\crayons.dm"
#include "yogstation\code\game\objects\items\dna_injector.dm"
#include "yogstation\code\game\objects\items\extinguisher.dm"
+#include "yogstation\code\game\objects\items\flag.dm"
#include "yogstation\code\game\objects\items\fryingpan.dm"
#include "yogstation\code\game\objects\items\manuals.dm"
#include "yogstation\code\game\objects\items\plushes.dm"
@@ -4148,6 +4159,7 @@
#include "yogstation\code\game\objects\items\tool_switcher.dm"
#include "yogstation\code\game\objects\items\tools.dm"
#include "yogstation\code\game\objects\items\toys.dm"
+#include "yogstation\code\game\objects\items\trash.dm"
#include "yogstation\code\game\objects\items\weaponry.dm"
#include "yogstation\code\game\objects\items\circuitboards\computer_circuitboards.dm"
#include "yogstation\code\game\objects\items\circuitboards\machine_circuitboards.dm"
@@ -4182,6 +4194,7 @@
#include "yogstation\code\game\objects\items\storage\mre.dm"
#include "yogstation\code\game\objects\items\storage\toolbox.dm"
#include "yogstation\code\game\objects\items\storage\uplink_kits.dm"
+#include "yogstation\code\game\objects\items\tanks\tank_types.dm"
#include "yogstation\code\game\objects\items\wielded\big_spoon.dm"
#include "yogstation\code\game\objects\items\wielded\sledgehammer.dm"
#include "yogstation\code\game\objects\items\wielded\vxtvulhammer.dm"
@@ -4198,8 +4211,10 @@
#include "yogstation\code\game\objects\structures\bar_stuff\bar_stuff.dm"
#include "yogstation\code\game\objects\structures\beds_chairs\chair.dm"
#include "yogstation\code\game\objects\structures\beds_chairs\electric_bed.dm"
+#include "yogstation\code\game\objects\structures\signs\_signs.dm"
#include "yogstation\code\game\objects\structures\signs\signs_plaques.dm"
#include "yogstation\code\game\turfs\change_turf.dm"
+#include "yogstation\code\game\turfs\open\floor\plating\asteroid.dm"
#include "yogstation\code\game\turfs\simulated\ballpit.dm"
#include "yogstation\code\game\turfs\simulated\minerals.dm"
#include "yogstation\code\game\turfs\simulated\floor\fancy_floor.dm"
@@ -4226,6 +4241,7 @@
#include "yogstation\code\modules\antagonists\_common\antag_menu.dm"
#include "yogstation\code\modules\antagonists\abductor\equipment\abduction_outfits.dm"
#include "yogstation\code\modules\antagonists\blob\blob\blobs\core.dm"
+#include "yogstation\code\modules\antagonists\changeling\changeling.dm"
#include "yogstation\code\modules\antagonists\darkspawn\_psi_web.dm"
#include "yogstation\code\modules\antagonists\darkspawn\darkspawn_antag.dm"
#include "yogstation\code\modules\antagonists\darkspawn\darkspawn_illusions.dm"
@@ -4280,14 +4296,21 @@
#include "yogstation\code\modules\antagonists\traitor\backstory\traitor_factions.dm"
#include "yogstation\code\modules\assembly\signaler.dm"
#include "yogstation\code\modules\atmospherics\airalarm.dm"
+#include "yogstation\code\modules\atmospherics\auxgm\breathing_classes.dm"
#include "yogstation\code\modules\atmospherics\machinery\pipes\bluespace.dm"
#include "yogstation\code\modules\atmospherics\unary_devices\vent_pump.dm"
#include "yogstation\code\modules\cargo\cargo_packs.dm"
+#include "yogstation\code\modules\cargo\bounties\medical.dm"
+#include "yogstation\code\modules\cargo\exports\organs.dm"
#include "yogstation\code\modules\cargo\exports\sheets.dm"
#include "yogstation\code\modules\cargo\exports\weapons.dm"
#include "yogstation\code\modules\client\client_defines.dm"
#include "yogstation\code\modules\client\client_procs.dm"
#include "yogstation\code\modules\client\preferences_savefile.dm"
+#include "yogstation\code\modules\client\preferences\_preference.dm"
+#include "yogstation\code\modules\client\preferences\clothing.dm"
+#include "yogstation\code\modules\client\preferences\species_features\mutants.dm"
+#include "yogstation\code\modules\client\preferences\species_features\vox.dm"
#include "yogstation\code\modules\client\verbs\afk.dm"
#include "yogstation\code\modules\client\verbs\antag_token.dm"
#include "yogstation\code\modules\client\verbs\looc.dm"
@@ -4300,24 +4323,36 @@
#include "yogstation\code\modules\clothing\mask.dm"
#include "yogstation\code\modules\clothing\shoe.dm"
#include "yogstation\code\modules\clothing\under.dm"
+#include "yogstation\code\modules\clothing\ears\_ears.dm"
#include "yogstation\code\modules\clothing\glasses\_glasses.dm"
+#include "yogstation\code\modules\clothing\gloves\_gloves.dm"
#include "yogstation\code\modules\clothing\gloves\miscellaneous.dm"
+#include "yogstation\code\modules\clothing\head\_head.dm"
#include "yogstation\code\modules\clothing\head\helmet.dm"
#include "yogstation\code\modules\clothing\head\jobs.dm"
#include "yogstation\code\modules\clothing\head\misc.dm"
#include "yogstation\code\modules\clothing\head\misc_special.dm"
+#include "yogstation\code\modules\clothing\masks\_masks.dm"
+#include "yogstation\code\modules\clothing\masks\breath.dm"
#include "yogstation\code\modules\clothing\masks\hailer.dm"
#include "yogstation\code\modules\clothing\neck\_neck.dm"
#include "yogstation\code\modules\clothing\outfits\event.dm"
+#include "yogstation\code\modules\clothing\shoes\_shoes.dm"
+#include "yogstation\code\modules\clothing\shoes\colour.dm"
+#include "yogstation\code\modules\clothing\shoes\magboots.dm"
#include "yogstation\code\modules\clothing\shoes\miscellaneous.dm"
#include "yogstation\code\modules\clothing\shoes\special_shoes.dm"
+#include "yogstation\code\modules\clothing\spacesuits\alien.dm"
#include "yogstation\code\modules\clothing\spacesuits\hardsuit.dm"
+#include "yogstation\code\modules\clothing\suits\_suits.dm"
#include "yogstation\code\modules\clothing\suits\armor.dm"
#include "yogstation\code\modules\clothing\suits\explorer_gear.dm"
#include "yogstation\code\modules\clothing\suits\labcoat.dm"
#include "yogstation\code\modules\clothing\suits\miscellaneous.dm"
#include "yogstation\code\modules\clothing\suits\nerd.dm"
#include "yogstation\code\modules\clothing\suits\wiz_robe.dm"
+#include "yogstation\code\modules\clothing\under\_under.dm"
+#include "yogstation\code\modules\clothing\under\color.dm"
#include "yogstation\code\modules\clothing\under\miscellaneous.dm"
#include "yogstation\code\modules\clothing\under\jobs\civilian.dm"
#include "yogstation\code\modules\clothing\under\jobs\engineering.dm"
@@ -4339,6 +4374,8 @@
#include "yogstation\code\modules\events\probabilistic_anomaly.dm"
#include "yogstation\code\modules\events\weightless.dm"
#include "yogstation\code\modules\food_and_drinks\food\condiment.dm"
+#include "yogstation\code\modules\food_and_drinks\food\snacks_meat.dm"
+#include "yogstation\code\modules\food_and_drinks\food\recipes\tablecraft\recipes_meat.dm"
#include "yogstation\code\modules\food_and_drinks\food\snacks\meat.dm"
#include "yogstation\code\modules\goals\station_goals\bluespace_tap.dm"
#include "yogstation\code\modules\guardian\guardian.dm"
@@ -4387,6 +4424,8 @@
#include "yogstation\code\modules\jungleland\kinetic_javelin.dm"
#include "yogstation\code\modules\language\darkspeak.dm"
#include "yogstation\code\modules\language\japanese.dm"
+#include "yogstation\code\modules\language\language_holder.dm"
+#include "yogstation\code\modules\language\voxpidgin.dm"
#include "yogstation\code\modules\mentor\follow.dm"
#include "yogstation\code\modules\mentor\mentor.dm"
#include "yogstation\code\modules\mentor\mentor_memo.dm"
@@ -4414,6 +4453,7 @@
#include "yogstation\code\modules\mob\living\carbon\alien\humanoid\humanoid.dm"
#include "yogstation\code\modules\mob\living\carbon\alien\humanoid\humanoid_defense.dm"
#include "yogstation\code\modules\mob\living\carbon\alien\humanoid\queen.dm"
+#include "yogstation\code\modules\mob\living\carbon\human\emote.dm"
#include "yogstation\code\modules\mob\living\carbon\human\human.dm"
#include "yogstation\code\modules\mob\living\carbon\human\human_defense.dm"
#include "yogstation\code\modules\mob\living\carbon\human\human_defines.dm"
@@ -4426,6 +4466,7 @@
#include "yogstation\code\modules\mob\living\carbon\human\species_types\lizard.dm"
#include "yogstation\code\modules\mob\living\carbon\human\species_types\plantpeople.dm"
#include "yogstation\code\modules\mob\living\carbon\human\species_types\szlachta.dm"
+#include "yogstation\code\modules\mob\living\carbon\human\species_types\vox.dm"
#include "yogstation\code\modules\mob\living\carbon\human\species_types\preternis\organs.dm"
#include "yogstation\code\modules\mob\living\carbon\human\species_types\preternis\power_suck.dm"
#include "yogstation\code\modules\mob\living\carbon\human\species_types\preternis\preternis.dm"
@@ -4479,6 +4520,7 @@
#include "yogstation\code\modules\reagents\chemistry\reagents\food_reagents.dm"
#include "yogstation\code\modules\reagents\chemistry\reagents\other_reagents.dm"
#include "yogstation\code\modules\reagents\chemistry\recipes\slime_extracts.dm"
+#include "yogstation\code\modules\reagents\reagent_containers\blood_pack.dm"
#include "yogstation\code\modules\reagents\reagent_containers\bottle.dm"
#include "yogstation\code\modules\reagents\reagent_containers\gummies.dm"
#include "yogstation\code\modules\reagents\reagent_containers\hypospray.dm"
@@ -4488,6 +4530,7 @@
#include "yogstation\code\modules\research\rdconsole.dm"
#include "yogstation\code\modules\research\designs\bluespace_designs.dm"
#include "yogstation\code\modules\research\designs\comp_board_designs.dm"
+#include "yogstation\code\modules\research\designs\limbgrower_designs.dm"
#include "yogstation\code\modules\research\designs\medical_designs.dm"
#include "yogstation\code\modules\research\designs\misc_designs.dm"
#include "yogstation\code\modules\research\designs\spacepod_designs.dm"
@@ -4531,10 +4574,18 @@
#include "yogstation\code\modules\spells\spell_types\projectile\animation.dm"
#include "yogstation\code\modules\spells\spell_types\self\cauterize.dm"
#include "yogstation\code\modules\spells\spell_types\shapeshift\mouse.dm"
-#include "yogstation\code\modules\surgery\bodypart.dm"
#include "yogstation\code\modules\surgery\gender_reassignment.dm"
+#include "yogstation\code\modules\surgery\bodyparts\_bodyparts.dm"
+#include "yogstation\code\modules\surgery\bodyparts\head.dm"
+#include "yogstation\code\modules\surgery\organs\appendix.dm"
+#include "yogstation\code\modules\surgery\organs\ears.dm"
+#include "yogstation\code\modules\surgery\organs\eyes.dm"
#include "yogstation\code\modules\surgery\organs\heart.dm"
+#include "yogstation\code\modules\surgery\organs\liver.dm"
#include "yogstation\code\modules\surgery\organs\lungs.dm"
+#include "yogstation\code\modules\surgery\organs\stomach.dm"
+#include "yogstation\code\modules\surgery\organs\tails.dm"
+#include "yogstation\code\modules\surgery\organs\tongue.dm"
#include "yogstation\code\modules\uplink\uplink_item.dm"
#include "yogstation\code\modules\vending\fishing.dm"
#include "yogstation\code\modules\vending\gift.dm"
diff --git a/yogstation/code/__HELPERS/mobs.dm b/yogstation/code/__HELPERS/mobs.dm
index 2f69fc04ac154..051949f879767 100644
--- a/yogstation/code/__HELPERS/mobs.dm
+++ b/yogstation/code/__HELPERS/mobs.dm
@@ -5,6 +5,25 @@
if(!findname(.))
break
+/proc/random_unique_vox_name(attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(vox_name())
+
+ if(!findname(.))
+ break
+
+GLOBAL_LIST_INIT(vox_skin_tones, sortList(list(
+ "lime",
+ "crimson",
+ "nebula",
+ "azure",
+ "emerald",
+ "brown",
+ "plum",
+ "grey",
+ "mossy"
+ )))
+
/proc/is_admin(user)
if(ismob(user))
var/mob/temp = user
diff --git a/yogstation/code/__HELPERS/names.dm b/yogstation/code/__HELPERS/names.dm
index fe32630465994..23861e1403c57 100644
--- a/yogstation/code/__HELPERS/names.dm
+++ b/yogstation/code/__HELPERS/names.dm
@@ -3,3 +3,10 @@
return "[pick(GLOB.gorilla_names_male)] [pick(GLOB.last_names)]"
else
return "[pick(GLOB.gorilla_names_female)] [pick(GLOB.last_names)]"
+
+/proc/vox_name()
+ var/sounds = rand(2,8)
+ var/vox_name = ""
+ for(var/sound in 1 to sounds)
+ vox_name += pick("ti","hi","ki","ya","ta","ha","ka","yi","chi","cha","kah")
+ return vox_name
diff --git a/yogstation/code/_globalvars/lists/flavor_misc.dm b/yogstation/code/_globalvars/lists/flavor_misc.dm
new file mode 100644
index 0000000000000..920f3c028b5ba
--- /dev/null
+++ b/yogstation/code/_globalvars/lists/flavor_misc.dm
@@ -0,0 +1,8 @@
+//Vox prefs and sprite accessories
+GLOBAL_LIST_EMPTY(vox_quills_list)
+GLOBAL_LIST_EMPTY(vox_facial_quills_list)
+GLOBAL_LIST_EMPTY(vox_tails_list)
+GLOBAL_LIST_EMPTY(vox_body_markings_list)
+GLOBAL_LIST_EMPTY(vox_tail_markings_list)
+GLOBAL_LIST_EMPTY(animated_vox_tails_list)
+GLOBAL_LIST_EMPTY(animated_vox_tail_markings_list)
diff --git a/yogstation/code/datums/blood_types.dm b/yogstation/code/datums/blood_types.dm
new file mode 100644
index 0000000000000..720f16bdbabb1
--- /dev/null
+++ b/yogstation/code/datums/blood_types.dm
@@ -0,0 +1,4 @@
+/datum/blood_type/vox
+ name = "V"
+ color = COLOR_BLOOD_VOX
+ compatible_types = list(/datum/blood_type/vox)
diff --git a/yogstation/code/datums/dog_fashion.dm b/yogstation/code/datums/dog_fashion.dm
new file mode 100644
index 0000000000000..9ab978ec2221f
--- /dev/null
+++ b/yogstation/code/datums/dog_fashion.dm
@@ -0,0 +1,3 @@
+/datum/dog_fashion/head/fried_vox_empty
+ name = "Colonel REAL_NAME"
+ desc = "Keep away from live Vox."
diff --git a/yogstation/code/datums/emotes.dm b/yogstation/code/datums/emotes.dm
new file mode 100644
index 0000000000000..c295343cfe409
--- /dev/null
+++ b/yogstation/code/datums/emotes.dm
@@ -0,0 +1,3 @@
+/datum/emote
+ /// Message to display if the user is a vox
+ var/message_vox = ""
diff --git a/yogstation/code/datums/traits/good.dm b/yogstation/code/datums/traits/good.dm
new file mode 100644
index 0000000000000..06779e2aae935
--- /dev/null
+++ b/yogstation/code/datums/traits/good.dm
@@ -0,0 +1,6 @@
+/datum/quirk/multilingual/voxpidgin
+ name = "Multilingual (Vox-pidgin)"
+ desc = "You spent a portion of your life learning to understand Vox-pidgin. You may or may not be able to speak it based on your anatomy."
+ specific = /datum/language/vox
+ gain_text = span_notice("You have learned to understand Vox-pidgin.")
+ lose_text = span_notice("You have forgotten how to understand Vox-pidgin.")
diff --git a/yogstation/code/game/objects/items.dm b/yogstation/code/game/objects/items.dm
new file mode 100644
index 0000000000000..f5b158c922182
--- /dev/null
+++ b/yogstation/code/game/objects/items.dm
@@ -0,0 +1,3 @@
+/obj/item
+ var/list/sprite_sheets = null
+ var/species_fitted = null
diff --git a/yogstation/code/game/objects/items/flag.dm b/yogstation/code/game/objects/items/flag.dm
new file mode 100644
index 0000000000000..c56520a5f54ba
--- /dev/null
+++ b/yogstation/code/game/objects/items/flag.dm
@@ -0,0 +1,299 @@
+/obj/item/flag
+ name = "flag"
+ desc = "It's a flag."
+ icon = 'icons/obj/flag.dmi'
+ icon_state = "ntflag"
+ lefthand_file = 'icons/mob/inhands/flags_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/flags_righthand.dmi'
+ w_class = WEIGHT_CLASS_BULKY
+ max_integrity = 40
+ resistance_flags = FLAMMABLE
+ custom_fire_overlay = "fire"
+ var/rolled = FALSE
+
+/obj/item/flag/attackby(obj/item/item, mob/user, params)
+ . = ..()
+ if(item.is_hot() && !(resistance_flags & ON_FIRE))
+ user.visible_message(span_notice("[user] lights [src] with [item]."), span_notice("You light [src] with [item]."), span_warning("You hear a low whoosh."))
+ fire_act()
+
+/obj/item/flag/attack_self(mob/user)
+ rolled = !rolled
+ user.visible_message(span_notice("[user] [rolled ? "rolls up" : "unfurls"] [src]."), span_notice("You [rolled ? "roll up" : "unfurl"] [src]."), span_warning("You hear fabric rustling."))
+ update_icon()
+
+/obj/item/flag/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume, global_overlay = FALSE)
+ ..()
+ update_icon()
+
+/obj/item/flag/extinguish()
+ ..()
+ update_icon()
+
+/obj/item/flag/update_icon_state()
+ . = ..()
+ icon_state = initial(icon_state)
+ item_state = icon_state
+ if(rolled)
+ icon_state = "[icon_state]_rolled"
+ custom_fire_overlay = "fire_rolled"
+ else
+ custom_fire_overlay = initial(custom_fire_overlay)
+ if(resistance_flags & ON_FIRE)
+ item_state = "[item_state]_fire"
+ if(ismob(loc))
+ var/mob/M = loc
+ M.update_inv_hands()
+
+/obj/item/flag/nt
+ name = "\improper Nanotrasen flag"
+ desc = "A flag proudly boasting the logo of NT."
+ icon_state = "ntflag"
+
+/obj/item/flag/clown
+ name = "\improper Clown Planet flag"
+ desc = "The banner of His Majesty, King Squiggles the Eighth."
+ icon_state = "clownflag"
+
+/obj/item/flag/mime
+ name = "\improper Mime Revolution flag"
+ desc = "The banner of the glorious revolutionary forces fighting the oppressors on Clown Planet."
+ icon_state = "mimeflag"
+
+/obj/item/flag/ian
+ name = "\improper Ian flag"
+ desc = "The banner of Ian, because SQUEEEEE."
+ icon_state = "ianflag"
+
+
+//Species flags
+
+/obj/item/flag/species/slime
+ name = "\improper Slime People flag"
+ desc = "A flag proudly proclaiming the superior heritage of Slime People."
+ icon_state = "slimeflag"
+
+/obj/item/flag/species/skrell
+ //name = "\improper Skrell flag"
+ //desc = "A flag proudly proclaiming the superior heritage of Skrell."
+ icon_state = "skrellflag"
+
+/obj/item/flag/species/vox
+ name = "\improper Vox flag"
+ desc = "A flag proudly proclaiming the superior heritage of Vox."
+ icon_state = "voxflag"
+
+/obj/item/flag/species/machine
+ name = "\improper Synthetics flag"
+ desc = "A flag proudly proclaiming the superior heritage of Synthetics."
+ icon_state = "machineflag"
+
+/obj/item/flag/species/diona
+ //name = "\improper Diona flag" //phytosians maybe?
+ //desc = "A flag proudly proclaiming the superior heritage of Dionae."
+ icon_state = "dionaflag"
+
+/obj/item/flag/species/human
+ name = "\improper Human flag"
+ desc = "A flag proudly proclaiming the superior heritage of Humans."
+ icon_state = "humanflag"
+
+/obj/item/flag/species/greys
+ //name = "\improper Greys flag"
+ //desc = "A flag proudly proclaiming the superior heritage of Greys."
+ icon_state = "greysflag"
+
+/obj/item/flag/species/kidan
+ //name = "\improper Kidan flag" //preternis maybe?
+ //desc = "A flag proudly proclaiming the superior heritage of Kidan."
+ icon_state = "kidanflag"
+
+/obj/item/flag/species/taj
+ //name = "\improper Tajaran flag"
+ //desc = "A flag proudly proclaiming the superior heritage of Tajaran."
+ icon_state = "tajflag"
+
+/obj/item/flag/species/lizard
+ name = "\improper Vuulek flag"
+ desc = "A flag proudly proclaiming the superior heritage of Vuulek."
+ icon_state = "lizardflag"
+
+/obj/item/flag/species/vulp
+ //name = "\improper Vulpkanin flag"
+ //desc = "A flag proudly proclaiming the superior heritage of Vulpkanin."
+ icon_state = "vulpflag"
+
+/obj/item/flag/species/drask
+ //name = "\improper Drask flag"
+ //desc = "A flag proudly proclaiming the superior heritage of Drask."
+ icon_state = "draskflag"
+
+/obj/item/flag/species/plasma
+ name = "\improper Plasmaman flag"
+ desc = "A flag proudly proclaiming the superior heritage of Plasmamen."
+ icon_state = "plasmaflag"
+
+/obj/item/flag/species/moth
+ name ="\improper Ex'hai flag"
+ desc = "An eccentric handmade standard, luxuriously soft due to exotic silks and embossed with lustrous gold. Although inspired by the pride that Ex'hai take in their baubles, it ultimately feels melancholic. Beauty knows no pain, afterall."
+ icon_state = "mothflag"
+
+//Department Flags
+
+/obj/item/flag/cargo
+ name = "\improper Cargonia flag"
+ desc = "The flag of the independent, sovereign nation of Cargonia."
+ icon_state = "cargoflag"
+
+/obj/item/flag/med
+ name = "\improper Medistan flag"
+ desc = "The flag of the independent, sovereign nation of Medistan."
+ icon_state = "medflag"
+
+/obj/item/flag/sec
+ name = "\improper Brigston flag"
+ desc = "The flag of the independent, sovereign nation of Brigston."
+ icon_state = "secflag"
+
+/obj/item/flag/rnd
+ name = "\improper Scientopia flag"
+ desc = "The flag of the independent, sovereign nation of Scientopia."
+ icon_state = "rndflag"
+
+/obj/item/flag/atmos
+ name = "\improper Atmosia flag"
+ desc = "The flag of the independent, sovereign nation of Atmosia."
+ icon_state = "atmosflag"
+
+/obj/item/flag/command
+ name = "\improper Command flag"
+ desc = "The flag of the independent, sovereign nation of Command."
+ icon_state = "ntflag"
+
+//Antags
+
+/obj/item/flag/grey
+ name = "\improper Greytide flag"
+ desc = "A banner made from an old grey jumpsuit."
+ icon_state = "greyflag"
+
+/obj/item/flag/syndi
+ name = "\improper Syndicate flag"
+ desc = "A flag proudly boasting the logo of the Syndicate, in defiance of NT."
+ icon_state = "syndiflag"
+
+/obj/item/flag/wiz
+ name = "\improper Wizard Federation flag"
+ desc = "A flag proudly boasting the logo of the Wizard Federation, sworn enemies of NT."
+ icon_state = "wizflag"
+
+/obj/item/flag/cult
+ name = "\improper Nar'Sie Cultist flag"
+ desc = "A flag proudly boasting the logo of the cultists, sworn enemies of NT."
+ icon_state = "cultflag"
+
+/obj/item/flag/ussp
+ name = "\improper USSP flag"
+ desc = "A flag proudly boasting the logo of the USSP, a noticeable faction in the galaxy."
+ icon_state = "usspflag"
+
+/obj/item/flag/solgov
+ name = "\improper Trans-Solar Federation flag"
+ desc = "A flag proudly boasting the logo of the SolGov, allied to NT government originated from Earth."
+ icon_state = "solgovflag"
+
+//Chameleon
+/*
+/obj/item/flag/chameleon
+ name = "chameleon flag"
+ desc = "A poor recreation of the official NT flag. It seems to shimmer a little."
+ icon_state = "ntflag"
+ origin_tech = "syndicate=1;magnets=4"
+ var/updated_icon_state = null
+ var/used = FALSE
+ var/obj/item/grenade/boobytrap = null
+ var/mob/trapper = null
+
+/obj/item/flag/chameleon/New()
+ updated_icon_state = icon_state
+ ..()
+
+/obj/item/flag/chameleon/attack_self(mob/user)
+ if(used)
+ return ..()
+
+ var/list/flag_types = typesof(/obj/item/flag) - list(/obj/item/flag, /obj/item/flag/chameleon, /obj/item/flag/chameleon/depot)
+ var/list/flag = list()
+
+ for(var/flag_type in flag_types)
+ var/obj/item/flag/F = new flag_type
+ flag[capitalize(F.name)] = F
+
+ var/list/show_flag = list("EXIT" = null) + sortList(flag)
+
+ var/input_flag = input(user, "Choose a flag to disguise as.", "Choose a flag.") in show_flag
+
+ if(user && (src in user.contents))
+
+ var/obj/item/flag/chosen_flag = flag[input_flag]
+
+ if(chosen_flag && !used)
+ name = chosen_flag.name
+ icon_state = chosen_flag.icon_state
+ updated_icon_state = icon_state
+ desc = chosen_flag.desc
+ used = TRUE
+
+/obj/item/flag/chameleon/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/grenade) && !boobytrap)
+ if(user.drop_item())
+ boobytrap = I
+ trapper = user
+ I.forceMove(src)
+ to_chat(user, "You hide [I] in [src]. It will detonate some time after the flag is lit on fire.")
+ var/turf/bombturf = get_turf(src)
+ var/area/A = get_area(bombturf)
+ log_game("[key_name(user)] has hidden [I] in [src] ready for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]).")
+ investigate_log("[key_name(user)] has hidden [I] in [src] ready for detonation at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]).", INVESTIGATE_BOMB)
+ add_attack_logs(user, src, "has hidden [I] ready for detonation in", ATKLOG_MOST)
+ else if(I.get_heat() && !(resistance_flags & ON_FIRE) && boobytrap && trapper)
+ var/turf/bombturf = get_turf(src)
+ var/area/A = get_area(bombturf)
+ log_game("[key_name_admin(user)] has lit [src] trapped with [boobytrap] by [key_name_admin(trapper)] at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]).")
+ investigate_log("[key_name_admin(user)] has lit [src] trapped with [boobytrap] by [key_name_admin(trapper)] at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]).", INVESTIGATE_BOMB)
+ add_attack_logs(user, src, "has lit (booby trapped with [boobytrap]", ATKLOG_FEW)
+ burn()
+ else
+ return ..()
+
+/obj/item/flag/chameleon/screwdriver_act(mob/user, obj/item/I)
+ if(!boobytrap || user != trapper)
+ return
+ . = TRUE
+ if(!I.use_tool(src, user, 0, volume = I.tool_volume))
+ return
+ to_chat(user, "You remove [boobytrap] from [src].")
+ boobytrap.forceMove(get_turf(src))
+ boobytrap = null
+ trapper = null
+
+/obj/item/flag/chameleon/burn()
+ if(boobytrap)
+ fire_act()
+ addtimer(CALLBACK(src, PROC_REF(prime_boobytrap)), boobytrap.det_time)
+ else
+ ..()
+
+/obj/item/flag/chameleon/proc/prime_boobytrap()
+ boobytrap.forceMove(get_turf(loc))
+ boobytrap.prime()
+ boobytrap = null
+ burn()
+
+/obj/item/flag/chameleon/updateFlagIcon()
+ icon_state = updated_icon_state
+
+/obj/item/flag/chameleon/depot/New()
+ ..()
+ boobytrap = new /obj/item/grenade/gas/plasma(src)
+*/
diff --git a/yogstation/code/game/objects/items/plushes.dm b/yogstation/code/game/objects/items/plushes.dm
index e8948ea04e3c8..1f72cd81668b1 100644
--- a/yogstation/code/game/objects/items/plushes.dm
+++ b/yogstation/code/game/objects/items/plushes.dm
@@ -176,4 +176,11 @@
squeak_override = list(
'sound/weapons/egloves.ogg' = 2,
'sound/weapons/cablecuff.ogg' = 1
- )
\ No newline at end of file
+ )
+
+/obj/item/toy/plush/voxplushie
+ name = "vox plushie"
+ desc = "A stitched-together Vox, fresh from the skipjack. Press its belly to hear it skree!"
+ icon_state = "plushie_vox"
+ item_state = "plushie_vox"
+ squeak_override = list('sound/voice/vox/shriek1.ogg' = 1)
diff --git a/yogstation/code/game/objects/items/stacks/sheets/leather.dm b/yogstation/code/game/objects/items/stacks/sheets/leather.dm
index 61b7a43e7ed23..87e7c2d6f964d 100644
--- a/yogstation/code/game/objects/items/stacks/sheets/leather.dm
+++ b/yogstation/code/game/objects/items/stacks/sheets/leather.dm
@@ -10,4 +10,10 @@
desc = "This would make a nice rug."
singular_name = "gorilla skin piece"
icon_state = "sheet-gorilla"
- item_state = "sheet-gorilla"
\ No newline at end of file
+ item_state = "sheet-gorilla"
+
+/obj/item/stack/sheet/animalhide/vox
+ name = "vox hide"
+ desc = "SKREE!"
+ singular_name = "vox hide piece"
+ icon_state = "sheet-vox"
diff --git a/yogstation/code/game/objects/items/storage/backpack.dm b/yogstation/code/game/objects/items/storage/backpack.dm
index b79d23c427539..87a0f08483e97 100644
--- a/yogstation/code/game/objects/items/storage/backpack.dm
+++ b/yogstation/code/game/objects/items/storage/backpack.dm
@@ -1,3 +1,6 @@
+/obj/item/storage/backpack
+ sprite_sheets = list(SPECIES_VOX = VOX_BACK_FILE)
+
/obj/item/storage/backpack/holding
icon = 'yogstation/icons/obj/storage.dmi'
icon_state = "holdingpack"
diff --git a/yogstation/code/game/objects/items/tanks/tank_types.dm b/yogstation/code/game/objects/items/tanks/tank_types.dm
new file mode 100644
index 0000000000000..da9c60b792b07
--- /dev/null
+++ b/yogstation/code/game/objects/items/tanks/tank_types.dm
@@ -0,0 +1,26 @@
+/obj/item/tank/internals/nitrogen
+ name = "nitrogen tank"
+ desc = "A tank of nitrogen."
+ icon_state = "oxygen_fr"
+ force = 10
+ distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE
+
+/obj/item/tank/internals/nitrogen/populate_gas()
+ air_contents.set_moles(GAS_N2, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+
+/obj/item/tank/internals/emergency_oxygen/nitrogen
+ name = "emergency nitrogen tank"
+ desc = "An emergency tank designed specifically for Vox."
+ icon_state = "emergency_nitrogen"
+
+/obj/item/tank/internals/emergency_oxygen/nitrogen/populate_gas()
+ air_contents.set_moles(GAS_N2, (10*ONE_ATMOSPHERE)* volume/(R_IDEAL_GAS_EQUATION*T20C))
+
+/obj/item/tank/internals/emergency_oxygen/vox
+ name = "vox specialized nitrogen tank"
+ desc = "A high-tech nitrogen tank designed specifically for Vox."
+ icon_state = "emergency_vox"
+ volume = 35
+
+/obj/item/tank/internals/emergency_oxygen/vox/populate_gas()
+ air_contents.set_moles(GAS_N2, (10*ONE_ATMOSPHERE)* volume/(R_IDEAL_GAS_EQUATION*T20C))
diff --git a/yogstation/code/game/objects/items/trash.dm b/yogstation/code/game/objects/items/trash.dm
new file mode 100644
index 0000000000000..88a6ffaa1b23e
--- /dev/null
+++ b/yogstation/code/game/objects/items/trash.dm
@@ -0,0 +1,6 @@
+/obj/item/trash/fried_vox
+ name = "Kentucky Fried Vox"
+ icon_state = "fried_vox_empty"
+ item_state = "fried_vox_empty"
+ slot_flags = ITEM_SLOT_HEAD
+ dog_fashion = /datum/dog_fashion/head/fried_vox_empty
diff --git a/yogstation/code/game/objects/objs.dm b/yogstation/code/game/objects/objs.dm
new file mode 100644
index 0000000000000..d5e06d66cc204
--- /dev/null
+++ b/yogstation/code/game/objects/objs.dm
@@ -0,0 +1,2 @@
+/obj
+ var/custom_fire_overlay // Update_fire_overlay will check if a different icon state should be used
diff --git a/yogstation/code/game/objects/structures/signs/_signs.dm b/yogstation/code/game/objects/structures/signs/_signs.dm
new file mode 100644
index 0000000000000..d1351d3b841fd
--- /dev/null
+++ b/yogstation/code/game/objects/structures/signs/_signs.dm
@@ -0,0 +1,19 @@
+/obj/structure/sign/pox
+ name = "NO VOX ALLOWED"
+ icon_state = "novox1-b"
+ desc = "A sign which reads 'NO VOX ALLOWED'."
+
+/obj/structure/sign/pox/no_cross
+ icon_state = "novox2-b"
+
+/obj/structure/sign/pox/red
+ icon_state = "novox1-r"
+
+/obj/structure/sign/pox/red/no_cross
+ icon_state = "novox2-r"
+
+/obj/structure/sign/pox/red/cicle
+ icon_state = "novox_circle1"
+
+/obj/structure/sign/pox/red/cicle/no_cross
+ icon_state = "novox_circle2"
diff --git a/yogstation/code/game/turfs/open/floor/plating/asteroid.dm b/yogstation/code/game/turfs/open/floor/plating/asteroid.dm
new file mode 100644
index 0000000000000..a042e95179576
--- /dev/null
+++ b/yogstation/code/game/turfs/open/floor/plating/asteroid.dm
@@ -0,0 +1 @@
+#define BREATH_VOX /datum/breathing_class/vox
diff --git a/yogstation/code/modules/antagonists/changeling/changeling.dm b/yogstation/code/modules/antagonists/changeling/changeling.dm
new file mode 100644
index 0000000000000..4cc43fb18adc4
--- /dev/null
+++ b/yogstation/code/modules/antagonists/changeling/changeling.dm
@@ -0,0 +1,2 @@
+/datum/changelingprofile
+ var/list/sprite_sheets_list = list()
diff --git a/yogstation/code/modules/atmospherics/auxgm/breathing_classes.dm b/yogstation/code/modules/atmospherics/auxgm/breathing_classes.dm
new file mode 100644
index 0000000000000..dda45a896de5d
--- /dev/null
+++ b/yogstation/code/modules/atmospherics/auxgm/breathing_classes.dm
@@ -0,0 +1,12 @@
+/datum/breathing_class/vox
+ gases = list(
+ GAS_N2 = 1,
+ GAS_CO2 = -0.7,
+ )
+ products = list(
+ GAS_CO2 = 1
+ )
+ low_alert_category = "not_enough_nitro"
+ low_alert_datum = /atom/movable/screen/alert/not_enough_nitro
+ high_alert_category = "too_much_nitro"
+ high_alert_datum = /atom/movable/screen/alert/too_much_nitro
diff --git a/yogstation/code/modules/cargo/bounties/medical.dm b/yogstation/code/modules/cargo/bounties/medical.dm
new file mode 100644
index 0000000000000..1916f7a4aba75
--- /dev/null
+++ b/yogstation/code/modules/cargo/bounties/medical.dm
@@ -0,0 +1,5 @@
+/datum/bounty/item/medical/vox_tail
+ name = "Vox Tail"
+ description = "Negotiations have broken down between Central Command and a group of Vox raiders. Ship us a vox tail so we can show them we mean business."
+ reward = 3000
+ wanted_types = list(/obj/item/organ/tail/vox)
diff --git a/yogstation/code/modules/cargo/cargo_packs.dm b/yogstation/code/modules/cargo/cargo_packs.dm
index 638d4b90c0ea8..68905411b5c21 100644
--- a/yogstation/code/modules/cargo/cargo_packs.dm
+++ b/yogstation/code/modules/cargo/cargo_packs.dm
@@ -106,6 +106,7 @@
/obj/item/toy/plush/inorixplushie,
/obj/item/toy/plush/beeplushie,
/obj/item/toy/plush/slimeplushie,
+ /obj/item/toy/plush/voxplushie,
/obj/item/toy/plush/cdragon)
crate_name = "plush crate"
crate_type = /obj/structure/closet/crate/wooden
diff --git a/yogstation/code/modules/cargo/exports/organs.dm b/yogstation/code/modules/cargo/exports/organs.dm
new file mode 100644
index 0000000000000..5440334e34b83
--- /dev/null
+++ b/yogstation/code/modules/cargo/exports/organs.dm
@@ -0,0 +1,4 @@
+/datum/export/organ/mutant/vox_tail
+ cost = 300
+ unit_name = "vox tail"
+ export_types = list(/obj/item/organ/tail/vox)
diff --git a/yogstation/code/modules/cargo/exports/sheets.dm b/yogstation/code/modules/cargo/exports/sheets.dm
index e2718f56ca14b..b2a65d50088d3 100644
--- a/yogstation/code/modules/cargo/exports/sheets.dm
+++ b/yogstation/code/modules/cargo/exports/sheets.dm
@@ -1,4 +1,10 @@
/datum/export/stack/skin/gorilla
cost = 150
unit_name = "gorilla hide"
- export_types = list(/obj/item/stack/sheet/animalhide/gorilla)
\ No newline at end of file
+ export_types = list(/obj/item/stack/sheet/animalhide/gorilla)
+
+/datum/export/stack/skin/vox
+ cost = 2500
+ export_limit = 200
+ unit_name = "vox hide"
+ export_types = list(/obj/item/stack/sheet/animalhide/vox)
diff --git a/yogstation/code/modules/client/preferences/_preference.dm b/yogstation/code/modules/client/preferences/_preference.dm
new file mode 100644
index 0000000000000..0df2a785aeb1a
--- /dev/null
+++ b/yogstation/code/modules/client/preferences/_preference.dm
@@ -0,0 +1,2 @@
+/datum/preference
+ var/list/blacklisted_species = list()
diff --git a/yogstation/code/modules/client/preferences/clothing.dm b/yogstation/code/modules/client/preferences/clothing.dm
new file mode 100644
index 0000000000000..9eebd30d1d6a6
--- /dev/null
+++ b/yogstation/code/modules/client/preferences/clothing.dm
@@ -0,0 +1,8 @@
+/datum/preference/choiced/socks
+ blacklisted_species = list(/datum/species/vox)
+
+/datum/preference/choiced/undershirt
+ blacklisted_species = list(/datum/species/vox)
+
+/datum/preference/choiced/underwear
+ blacklisted_species = list(/datum/species/vox)
diff --git a/yogstation/code/modules/client/preferences/species_features/mutants.dm b/yogstation/code/modules/client/preferences/species_features/mutants.dm
new file mode 100644
index 0000000000000..d9db94b95fbb0
--- /dev/null
+++ b/yogstation/code/modules/client/preferences/species_features/mutants.dm
@@ -0,0 +1,15 @@
+/datum/preference/color/mutant_color
+ blacklisted_species = list(/datum/species/vox)
+
+/datum/preference/color/mutant_color_secondary
+ savefile_key = "feature_mcolor_secondary"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ relevant_species_trait = MUTCOLORS_SECONDARY
+ blacklisted_species = list(/datum/species/vox)
+
+/datum/preference/color/mutant_color_secondary/create_default_value()
+ return sanitize_hexcolor("[pick("7F", "FF")][pick("7F", "FF")][pick("7F", "FF")]")
+
+/datum/preference/color/mutant_color_secondary/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["mcolor_secondary"] = value
diff --git a/yogstation/code/modules/client/preferences/species_features/vox.dm b/yogstation/code/modules/client/preferences/species_features/vox.dm
new file mode 100644
index 0000000000000..3b08c4b224ea1
--- /dev/null
+++ b/yogstation/code/modules/client/preferences/species_features/vox.dm
@@ -0,0 +1,255 @@
+/proc/generate_vox_side_shots(list/sprite_accessories, accessory_color = COLOR_DARKER_BROWN)
+ var/list/values = list()
+ var/icon/vox_head = icon('icons/mob/species/vox/bodyparts.dmi', "vox_head_lime")
+ var/icon/eyes = icon('icons/mob/species/vox/eyes.dmi', "eyes")
+ var/icon/eyes_static = icon('icons/mob/species/vox/eyes.dmi', "eyes_static_green")
+ eyes.Blend(COLOR_CYAN, ICON_MULTIPLY)
+ vox_head.Blend(eyes, ICON_OVERLAY)
+ vox_head.Blend(eyes_static, ICON_OVERLAY)
+ var/icon/beak = icon('icons/mob/species/vox/bodyparts.dmi', "vox_head_static")
+ vox_head.Blend(beak, ICON_OVERLAY)
+ for(var/name in sprite_accessories)
+ var/datum/sprite_accessory/sprite_accessory = sprite_accessories[name]
+ var/icon/final_icon = icon(vox_head)
+ if(name != "None")
+ var/icon/accessory_icon = icon(sprite_accessory.icon, sprite_accessory.icon_state)
+ accessory_icon.Blend(accessory_color, sprite_accessory.color_blend_mode == COLOR_BLEND_ADD ? ICON_ADD : ICON_MULTIPLY)
+ final_icon.Blend(accessory_icon, ICON_OVERLAY)
+ final_icon.Crop(10, 19, 22, 31)
+ final_icon.Scale(32, 32)
+ values[name] = final_icon
+ return values
+
+/datum/preference/choiced/vox_skin_tone
+ savefile_key = "feature_vox_skin_tone"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ relevant_mutant_bodypart = "vox_tail"
+ main_feature_name = "Skin Tone"
+
+/datum/preference/choiced/vox_skin_tone/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["vox_skin_tone"] = value
+
+/datum/preference/choiced/vox_skin_tone/init_possible_values()
+ return GLOB.vox_skin_tones
+
+/datum/preference/choiced/vox_skin_tone/compile_constant_data()
+ var/list/data = ..()
+ var/list/capitalized_skin_tones = list()
+ for(var/skin_tone in GLOB.vox_skin_tones)
+ capitalized_skin_tones[skin_tone] = capitalize(skin_tone)
+ data[CHOICED_PREFERENCE_DISPLAY_NAMES] = capitalized_skin_tones
+ var/list/skin_tones_to_hex = list(
+ "lime" = "#617b0f",
+ "crimson" = "#a32e2e",
+ "plum" = "#564759",
+ "azure" = "#124746",
+ "emerald" = "#04572d",
+ "brown" = "#774c22",
+ "grey" = "#4a514b",
+ "nebula" = "#5a4787",
+ "mossy" = "#626d0d"
+ )
+ var/list/to_hex = list()
+ for (var/choice in get_choices())
+ var/hex_value = skin_tones_to_hex[choice]
+ var/list/hsl = rgb2num(hex_value, COLORSPACE_HSL)
+ to_hex[choice] = list(
+ "lightness" = hsl[3],
+ "value" = hex_value,
+ )
+ data["to_hex"] = to_hex
+ return data
+
+/datum/preference/choiced/vox_tail_markings
+ savefile_key = "feature_vox_tail_markings"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ relevant_mutant_bodypart = "vox_tail_markings"
+
+/datum/preference/choiced/vox_tail_markings/init_possible_values()
+ return assoc_to_keys(GLOB.vox_tail_markings_list)
+
+/datum/preference/choiced/vox_tail_markings/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["vox_tail_markings"] = value
+
+/datum/preference/choiced/vox_body_markings
+ savefile_key = "feature_vox_body_markings"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ relevant_mutant_bodypart = "vox_body_markings"
+
+/datum/preference/choiced/vox_body_markings/init_possible_values()
+ return assoc_to_keys(GLOB.vox_body_markings_list)
+
+/datum/preference/choiced/vox_body_markings/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["vox_body_markings"] = value
+
+/datum/preference/choiced/vox_quills
+ savefile_key = "feature_vox_quills"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_FEATURES
+ should_generate_icons = TRUE
+ relevant_mutant_bodypart = "vox_quills"
+ main_feature_name = "Quills"
+
+/datum/preference/choiced/vox_quills/init_possible_values()
+ return generate_vox_side_shots(GLOB.vox_quills_list)
+
+/datum/preference/choiced/vox_quills/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["vox_quills"] = value
+
+/datum/preference/choiced/vox_quills/compile_constant_data()
+ var/list/data = ..()
+ data[SUPPLEMENTAL_FEATURE_KEY] = "feature_quill_color"
+ return data
+
+/datum/preference/choiced/vox_facial_quills
+ savefile_key = "feature_vox_facial_quills"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_FEATURES
+ should_generate_icons = TRUE
+ relevant_mutant_bodypart = "vox_facial_quills"
+ main_feature_name = "Facial Quills"
+
+/datum/preference/choiced/vox_facial_quills/init_possible_values()
+ return generate_vox_side_shots(GLOB.vox_facial_quills_list)
+
+/datum/preference/choiced/vox_facial_quills/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["vox_facial_quills"] = value
+
+/datum/preference/choiced/vox_facial_quills/compile_constant_data()
+ var/list/data = ..()
+ data[SUPPLEMENTAL_FEATURE_KEY] = "feature_facial_quill_color"
+ return data
+
+/datum/preference/color/hair_color/vox
+ category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
+ savefile_key = "feature_quill_color"
+ relevant_species_trait = null
+ relevant_mutant_bodypart = "vox_quills"
+ unique = TRUE
+
+/datum/preference/color/facial_hair_color/vox
+ category = PREFERENCE_CATEGORY_SUPPLEMENTAL_FEATURES
+ savefile_key = "feature_facial_quill_color"
+ relevant_species_trait = null
+ relevant_mutant_bodypart = "vox_facial_quills"
+ unique = TRUE
+
+/datum/preference/color/mutant_color/vox_body_markings_color
+ savefile_key = "feature_body_markings_color"
+ relevant_mutant_bodypart = "vox_body_markings"
+ relevant_species_trait = null
+ blacklisted_species = null
+ unique = TRUE
+
+/datum/preference/color/mutant_color/vox_body_markings_color/is_valid(value)
+ return findtext(value, GLOB.is_color)
+
+/datum/preference/color/mutant_color_secondary/vox_tail_markings_color
+ savefile_key = "feature_tail_markings_color"
+ relevant_mutant_bodypart = "vox_tail_markings"
+ relevant_species_trait = null
+ blacklisted_species = null
+ unique = TRUE
+
+/datum/preference/choiced/underwear/vox
+ savefile_key = "feature_vox_underwear"
+ relevant_mutant_bodypart = "vox_tail"
+ blacklisted_species = null
+ unique = TRUE
+
+/datum/preference/choiced/underwear/vox/init_possible_values()
+ return generate_values_for_underwear('icons/mob/clothing/species/vox/underwear.dmi', GLOB.underwear_list, list("vox_chest_lime", "vox_r_leg_lime", "vox_l_leg_lime", "vox_r_leg_static", "vox_l_leg_static"), 'icons/mob/species/vox/bodyparts.dmi')
+
+/datum/preference/choiced/socks/vox
+ savefile_key = "feature_vox_socks"
+ relevant_mutant_bodypart = "vox_tail"
+ blacklisted_species = null
+ unique = TRUE
+
+/datum/preference/choiced/socks/vox/init_possible_values()
+ return generate_values_for_underwear('icons/mob/clothing/species/vox/socks.dmi', GLOB.socks_list, list("vox_r_leg_lime", "vox_l_leg_lime", "vox_r_leg_static", "vox_l_leg_static"), 'icons/mob/species/vox/bodyparts.dmi')
+
+/datum/preference/choiced/undershirt/vox
+ savefile_key = "feature_vox_undershirt"
+ should_generate_icons = TRUE
+ relevant_mutant_bodypart = "vox_tail"
+ blacklisted_species = null
+ unique = TRUE
+
+/datum/preference/choiced/undershirt/vox/init_possible_values()
+ var/bodyparts_icon = 'icons/mob/species/vox/bodyparts.dmi'
+ var/icon/body = icon(bodyparts_icon, "vox_r_leg_lime")
+ body.Blend(icon(bodyparts_icon, "vox_l_leg_lime"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_r_arm_lime"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_l_arm_lime"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_r_hand"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_l_hand"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_chest_lime"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_l_arm_static"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_r_arm_static"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_l_leg_static"), ICON_OVERLAY)
+ body.Blend(icon(bodyparts_icon, "vox_r_leg_static"), ICON_OVERLAY)
+ var/vox_undershirt_icon = 'icons/mob/clothing/species/vox/undershirt.dmi'
+ var/list/values = list()
+ var/list/undershirt_list = GLOB.undershirt_list.Copy()
+ for(var/undershirt in undershirt_list)
+ if(undershirt == "Nude")
+ continue
+ var/datum/sprite_accessory/undershirt_accessory = undershirt_list[undershirt]
+ if(!icon_exists(vox_undershirt_icon, undershirt_accessory.icon_state))
+ undershirt_list -= undershirt
+ for(var/accessory_name in undershirt_list)
+ var/icon/icon_with_undershirt = icon(body)
+ if(accessory_name != "Nude")
+ var/datum/sprite_accessory/accessory = undershirt_list[accessory_name]
+ icon_with_undershirt.Blend(icon(vox_undershirt_icon, accessory.icon_state), ICON_OVERLAY)
+ icon_with_undershirt.Crop(9, 9, 23, 23)
+ icon_with_undershirt.Scale(32, 32)
+ values[accessory_name] = icon_with_undershirt
+ return values
+
+/datum/preference/choiced/vox_tank_type
+ savefile_key = "feature_vox_tank_type"
+ relevant_mutant_bodypart = "vox_tail"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+
+/datum/preference/choiced/vox_tank_type/init_possible_values()
+ return list("Large", "Specialized")
+
+/datum/preference/choiced/vox_tank_type/create_default_value()
+ return "Specialized"
+
+/datum/preference/choiced/vox_tank_type/apply_to_human()
+ return
+
+/datum/preference/choiced/vox_mask
+ savefile_key = "feature_vox_mask"
+ relevant_mutant_bodypart = "vox_tail"
+ savefile_identifier = PREFERENCE_CHARACTER
+ category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
+
+/datum/preference/choiced/vox_mask/init_possible_values()
+ return list("Breath Mask", "Respirator")
+
+/datum/preference/choiced/vox_mask/create_default_value()
+ return "Breath Mask"
+
+/datum/preference/choiced/vox_mask/apply_to_human()
+ return
+
+/datum/preference/choiced/hair_gradient/vox
+ savefile_key = "feature_quill_gradientstyle"
+ relevant_species_trait = null
+ relevant_mutant_bodypart = "vox_quills"
+ unique = TRUE
+
+/datum/preference/color/hair_gradient/vox
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ savefile_key = "feature_quill_gradientcolor"
+ relevant_species_trait = null
+ relevant_mutant_bodypart = "vox_quills"
+ unique = TRUE
diff --git a/yogstation/code/modules/clothing/clothing.dm b/yogstation/code/modules/clothing/clothing.dm
index d3cf29df1b586..5e66750d7cc25 100644
--- a/yogstation/code/modules/clothing/clothing.dm
+++ b/yogstation/code/modules/clothing/clothing.dm
@@ -1,3 +1,32 @@
+/obj/item/clothing
+ var/list/species_restricted = null //Only these species can wear this kit.
+
+//BS12: Species-restricted clothing check.
+/obj/item/clothing/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
+ //if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam)
+ if(!..())
+ return FALSE
+ // Skip species restriction checks on non-equipment slots
+ if(slot in list(ITEM_SLOT_BACKPACK, ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET))
+ return TRUE
+ if(species_restricted && ishuman(M))
+ var/wearable
+ var/exclusive
+ var/mob/living/carbon/human/H = M
+ if("exclude" in species_restricted)
+ exclusive = TRUE
+ if(H.dna.species)
+ if(exclusive)
+ if(!(H.dna.species.id in species_restricted))
+ wearable = TRUE
+ else
+ if(H.dna.species.id in species_restricted)
+ wearable = TRUE
+ if(!wearable)
+ to_chat(M, span_warning("Your species cannot wear [src]."))
+ return FALSE
+ return TRUE
+
/obj/item/clothing/ears/yogs
worn_icon = 'yogstation/icons/mob/clothing/ears/ears.dmi'
icon = 'yogstation/icons/obj/clothing/ears.dmi'
diff --git a/yogstation/code/modules/clothing/ears/_ears.dm b/yogstation/code/modules/clothing/ears/_ears.dm
new file mode 100644
index 0000000000000..240afdcf4cddc
--- /dev/null
+++ b/yogstation/code/modules/clothing/ears/_ears.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/ears
+ sprite_sheets = list(SPECIES_VOX = VOX_EARS_FILE)
diff --git a/yogstation/code/modules/clothing/glasses/_glasses.dm b/yogstation/code/modules/clothing/glasses/_glasses.dm
index 0481844811b64..63d733d5f1bc4 100644
--- a/yogstation/code/modules/clothing/glasses/_glasses.dm
+++ b/yogstation/code/modules/clothing/glasses/_glasses.dm
@@ -1,3 +1,6 @@
+/obj/item/clothing/glasses
+ sprite_sheets = list(SPECIES_VOX = VOX_EYES_FILE)
+
/obj/item/clothing/glasses/sunglasses/cheap
name = "cheap sunglasses"
desc = "Made in China."
diff --git a/yogstation/code/modules/clothing/gloves/_gloves.dm b/yogstation/code/modules/clothing/gloves/_gloves.dm
new file mode 100644
index 0000000000000..5ab19a74c0959
--- /dev/null
+++ b/yogstation/code/modules/clothing/gloves/_gloves.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/gloves
+ sprite_sheets = list(SPECIES_VOX = VOX_GLOVES_FILE)
diff --git a/yogstation/code/modules/clothing/gloves/miscellaneous.dm b/yogstation/code/modules/clothing/gloves/miscellaneous.dm
index f30b0e6b7063f..7754204b13f84 100644
--- a/yogstation/code/modules/clothing/gloves/miscellaneous.dm
+++ b/yogstation/code/modules/clothing/gloves/miscellaneous.dm
@@ -29,3 +29,11 @@
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
resistance_flags = NONE
+
+/obj/item/clothing/gloves/color/yellow/vox
+ name = "insulated gauntlets"
+ desc = "These bizarre gauntlets seem to be fitted for...bird claws?"
+ icon_state = "gloves-vox"
+ item_state = "gloves-vox"
+ icon = 'icons/obj/clothing/species/vox/gloves.dmi'
+ species_restricted = list(SPECIES_VOX)
diff --git a/yogstation/code/modules/clothing/head/_head.dm b/yogstation/code/modules/clothing/head/_head.dm
new file mode 100644
index 0000000000000..fa710799e9d67
--- /dev/null
+++ b/yogstation/code/modules/clothing/head/_head.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/head
+ sprite_sheets = list(SPECIES_VOX = VOX_HEAD_FILE)
diff --git a/yogstation/code/modules/clothing/head/helmet.dm b/yogstation/code/modules/clothing/head/helmet.dm
index 6a397a4aec93f..6b550b0aed7ac 100644
--- a/yogstation/code/modules/clothing/head/helmet.dm
+++ b/yogstation/code/modules/clothing/head/helmet.dm
@@ -1,4 +1,5 @@
/obj/item/clothing/head/helmet
+ sprite_sheets = list(SPECIES_VOX = VOX_HELMET_FILE)
var/initial_state
/obj/item/clothing/head/helmet/Initialize(mapload)
diff --git a/yogstation/code/modules/clothing/masks/_masks.dm b/yogstation/code/modules/clothing/masks/_masks.dm
new file mode 100644
index 0000000000000..c4e18c7b3fd9e
--- /dev/null
+++ b/yogstation/code/modules/clothing/masks/_masks.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/mask
+ sprite_sheets = list(SPECIES_VOX = VOX_MASK_FILE)
diff --git a/yogstation/code/modules/clothing/masks/breath.dm b/yogstation/code/modules/clothing/masks/breath.dm
new file mode 100644
index 0000000000000..c546778831760
--- /dev/null
+++ b/yogstation/code/modules/clothing/masks/breath.dm
@@ -0,0 +1,20 @@
+/obj/item/clothing/mask/breath/vox
+ name = "vox breath mask"
+ desc = "A weirdly-shaped breath mask."
+ icon_state = "voxmask"
+ species_restricted = list(SPECIES_VOX)
+ flags_cover = NONE
+ visor_flags_cover = NONE
+ actions_types = list()
+
+/obj/item/clothing/mask/breath/vox/attack_self(mob/user)
+ return
+
+/obj/item/clothing/mask/breath/vox/AltClick(mob/user)
+ return
+
+/obj/item/clothing/mask/breath/vox/respirator
+ name = "vox respirator"
+ desc = "A weirdly-shaped breath mask, this one seems to be designed for a vox beak."
+ icon_state = "voxmask2"
+ item_state = "voxmask2"
diff --git a/yogstation/code/modules/clothing/shoes/_shoes.dm b/yogstation/code/modules/clothing/shoes/_shoes.dm
new file mode 100644
index 0000000000000..31cb4a1d40b6c
--- /dev/null
+++ b/yogstation/code/modules/clothing/shoes/_shoes.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/shoes
+ sprite_sheets = list(SPECIES_VOX = VOX_SHOES_FILE)
diff --git a/yogstation/code/modules/clothing/shoes/colour.dm b/yogstation/code/modules/clothing/shoes/colour.dm
new file mode 100644
index 0000000000000..f072d8caf51e5
--- /dev/null
+++ b/yogstation/code/modules/clothing/shoes/colour.dm
@@ -0,0 +1,5 @@
+/obj/item/clothing/shoes/sneakers
+ sprite_sheets = list("GAGS_sprite" = list(SPECIES_VOX))
+
+/obj/item/clothing/shoes/sneakers/orange
+ sprite_sheets = list("GAGS_sprite" = list(SPECIES_VOX))
diff --git a/yogstation/code/modules/clothing/shoes/magboots.dm b/yogstation/code/modules/clothing/shoes/magboots.dm
new file mode 100644
index 0000000000000..74db4dc7fd684
--- /dev/null
+++ b/yogstation/code/modules/clothing/shoes/magboots.dm
@@ -0,0 +1,45 @@
+/obj/item/clothing/shoes/magboots/vox
+ name = "vox magclaws"
+ desc = "A pair of heavy, jagged armoured foot pieces, seemingly suitable for a velociraptor."
+ item_state = "boots-vox"
+ icon_state = "boots-vox"
+ icon = 'icons/obj/clothing/species/vox/shoes.dmi'
+ species_restricted = list(SPECIES_VOX)
+
+/obj/item/clothing/shoes/magboots/vox/attack_self(mob/user)
+ if(magpulse)
+ clothing_flags &= ~NOSLIP
+ REMOVE_TRAIT(src, TRAIT_NODROP, "vox_magclaws")
+ to_chat(user, "You relax your deathgrip on the flooring.")
+ else
+ //make sure these can only be used when equipped.
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/H = user
+ if(H.shoes != src)
+ to_chat(user, span_warning("You will have to put on [src] before you can do that."))
+ return
+ clothing_flags |= NOSLIP //kinda hard to take off magclaws when you are gripping them tightly.
+ ADD_TRAIT(src, TRAIT_NODROP, "vox_magclaws")
+ to_chat(user, "You dig your claws deeply into the flooring, bracing yourself.")
+ to_chat(user, "It would be hard to take off [src] without relaxing your grip first.")
+ magpulse = !magpulse
+ user.update_inv_shoes() //so our mob-overlays update
+ user.update_gravity(user.has_gravity())
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.build_all_button_icons()
+
+//In case they somehow come off while enabled.
+/obj/item/clothing/shoes/magboots/vox/dropped(mob/user)
+ ..()
+ if(magpulse)
+ user.visible_message("[src] go limp as they are removed from [usr]'s feet.", "[src] go limp as they are removed from your feet.")
+ magpulse = FALSE
+ clothing_flags &= ~NOSLIP
+ REMOVE_TRAIT(src, TRAIT_NODROP, "vox_magclaws")
+
+/obj/item/clothing/shoes/magboots/vox/examine(mob/user)
+ . = ..()
+ if(magpulse)
+ . += "It would be hard to take these off without relaxing your grip first."//theoretically this message should only be seen by the wearer when the claws are equipped.
diff --git a/yogstation/code/modules/clothing/spacesuits/alien.dm b/yogstation/code/modules/clothing/spacesuits/alien.dm
new file mode 100644
index 0000000000000..fa1c19291caee
--- /dev/null
+++ b/yogstation/code/modules/clothing/spacesuits/alien.dm
@@ -0,0 +1,68 @@
+// Vox space gear (vaccuum suit, low pressure armour)
+// Can't be equipped by any other species due to bone structure and vox cybernetics.
+
+/obj/item/clothing/suit/space/vox
+ w_class = WEIGHT_CLASS_NORMAL
+ allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword, \
+ /obj/item/restraints/handcuffs, /obj/item/tank/internals)
+ armor = list(MELEE = 40, BULLET = 40, LASER = 30, ENERGY = 15, BOMB = 30, BIO = 30, RAD = 30, FIRE = 80, ACID = 85)
+ icon = 'icons/obj/clothing/species/vox/suits.dmi'
+ species_restricted = list(SPECIES_VOX)
+ slowdown = 2
+
+/obj/item/clothing/head/helmet/space/vox
+ armor = list(MELEE = 40, BULLET = 40, LASER = 30, ENERGY = 15, BOMB = 30, BIO = 30, RAD = 30, FIRE = 80, ACID = 85)
+ clothing_flags = STOPSPRESSUREDAMAGE
+ flags_cover = HEADCOVERSEYES
+ icon = 'icons/obj/clothing/species/vox/hats.dmi'
+ species_restricted = list(SPECIES_VOX)
+ sprite_sheets = list(SPECIES_VOX = VOX_HEAD_FILE)
+
+/obj/item/clothing/head/helmet/space/vox/pressure
+ name = "alien helmet"
+ icon_state = "vox-pressure"
+ item_state = "vox-pressure"
+ desc = "Hey, wasn't this a prop in \'The Abyss\'?"
+
+/obj/item/clothing/suit/space/vox/pressure
+ name = "alien pressure suit"
+ icon_state = "vox-pressure"
+ item_state = "vox-pressure"
+ desc = "A huge, armoured, pressurized suit, designed for distinctly nonhuman proportions."
+
+/obj/item/clothing/head/helmet/space/vox/carapace
+ name = "alien visor"
+ icon_state = "vox-carapace"
+ item_state = "vox-carapace"
+ desc = "A glowing visor, perhaps stolen from a depressed Cylon."
+
+/obj/item/clothing/suit/space/vox/carapace
+ name = "alien carapace armour"
+ icon_state = "vox-carapace"
+ item_state = "vox-carapace"
+ desc = "An armoured, segmented carapace with glowing purple lights. It looks pretty run-down."
+ slowdown = 1
+
+/obj/item/clothing/head/helmet/space/vox/stealth
+ name = "alien stealth helmet"
+ icon_state = "vox-stealth"
+ item_state = "vox-stealth"
+ desc = "A smoothly contoured, matte-black alien helmet."
+
+/obj/item/clothing/suit/space/vox/stealth
+ name = "alien stealth suit"
+ icon_state = "vox-stealth"
+ item_state = "vox-stealth"
+ desc = "A sleek black suit. It seems to have a tail, and is very heavy."
+
+/obj/item/clothing/head/helmet/space/vox/medic
+ name = "alien goggled helmet"
+ icon_state = "vox-medic"
+ item_state = "vox-medic"
+ desc = "An alien helmet with enormous goggled lenses."
+
+/obj/item/clothing/suit/space/vox/medic
+ name = "alien armour"
+ icon_state = "vox-medic"
+ item_state = "vox-medic"
+ desc = "An almost organic looking nonhuman pressure suit."
diff --git a/yogstation/code/modules/clothing/suits/_suits.dm b/yogstation/code/modules/clothing/suits/_suits.dm
new file mode 100644
index 0000000000000..3772a3097154a
--- /dev/null
+++ b/yogstation/code/modules/clothing/suits/_suits.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/suit
+ sprite_sheets = list(SPECIES_VOX = VOX_SUIT_FILE)
diff --git a/yogstation/code/modules/clothing/suits/miscellaneous.dm b/yogstation/code/modules/clothing/suits/miscellaneous.dm
index 09dec5e8907e6..f5d7016d9b9df 100644
--- a/yogstation/code/modules/clothing/suits/miscellaneous.dm
+++ b/yogstation/code/modules/clothing/suits/miscellaneous.dm
@@ -403,3 +403,32 @@
desc = "You're taking the piss outta this, aren't you?"
icon_state = "urinal"
item_state = "urinal"
+
+/obj/item/clothing/suit/hooded/vox_robes
+ name = "alien hooded robes"
+ desc = "Large, comfortable robes worn by those who need a bit more covering. The thick fabric contains a pocket suitable for those that need their hands free during their work, while the cloth serves to cover scars or other injuries to the wearer's body."
+ icon = VOX_SUIT_FILE
+ icon_state = "vox-robes"
+ item_state = "vox-robes"
+ body_parts_covered = CHEST|GROIN|LEGS|ARMS
+ hoodtype = /obj/item/clothing/head/hooded/vox_robe_hood
+ species_restricted = list(SPECIES_VOX)
+
+/obj/item/clothing/head/hooded/vox_robe_hood
+ name = "alien hood"
+ desc = "The thick fabric of this hood serves a variety of purposes to the vox wearing it - serving as a method to hide a scarred face or a way to keep warm in the coldest areas onboard the ship."
+ icon = VOX_HEAD_FILE
+ icon_state = "vox-robes-hood"
+ item_state = "vox-robes-hood"
+ flags_inv = HIDEHAIR
+ flags_cover = HEADCOVERSEYES
+ species_restricted = list(SPECIES_VOX)
+
+/obj/item/clothing/suit/armor/vox_scrap
+ name = "rusted metal armor"
+ desc = "A hodgepodge of various pieces of metal scrapped together into a rudimentary vox-shaped piece of armor."
+ allowed = list(/obj/item/gun, /obj/item/tank)
+ armor = list(MELEE = 70, BULLET = 30, LASER = 20, ENERGY = 5, BOMB = 40, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 15) //Higher melee armor versus lower everything else.
+ icon_state = "vox-scrap"
+ body_parts_covered = CHEST|ARMS|GROIN|LEGS
+ species_restricted = list(SPECIES_VOX)
diff --git a/yogstation/code/modules/clothing/under/_under.dm b/yogstation/code/modules/clothing/under/_under.dm
new file mode 100644
index 0000000000000..9b8a4beeba166
--- /dev/null
+++ b/yogstation/code/modules/clothing/under/_under.dm
@@ -0,0 +1,2 @@
+/obj/item/clothing/under
+ sprite_sheets = list(SPECIES_VOX = VOX_UNIFORM_FILE)
diff --git a/yogstation/code/modules/clothing/under/color.dm b/yogstation/code/modules/clothing/under/color.dm
new file mode 100644
index 0000000000000..ece2b7d9933bc
--- /dev/null
+++ b/yogstation/code/modules/clothing/under/color.dm
@@ -0,0 +1,5 @@
+/obj/item/clothing/under/color
+ sprite_sheets = list("GAGS_sprite" = list(SPECIES_VOX))
+
+/obj/item/clothing/under/skirt/color
+ sprite_sheets = list("GAGS_sprite" = list(SPECIES_VOX))
diff --git a/yogstation/code/modules/clothing/under/miscellaneous.dm b/yogstation/code/modules/clothing/under/miscellaneous.dm
index 0ae9bc0d23803..193b7b42e5cd2 100644
--- a/yogstation/code/modules/clothing/under/miscellaneous.dm
+++ b/yogstation/code/modules/clothing/under/miscellaneous.dm
@@ -574,3 +574,28 @@
item_state = "hawaiian_skirt"
fitted = FEMALE_UNIFORM_TOP //no hole in the skirt
can_adjust = 0
+
+/obj/item/clothing/under/rank/prisoner
+ sprite_sheets = list("GAGS_sprite" = list(SPECIES_VOX))
+
+/obj/item/clothing/under/vox
+ icon = 'icons/obj/clothing/species/vox/uniforms.dmi'
+ species_restricted = list(SPECIES_VOX)
+
+/obj/item/clothing/under/vox/vox_casual
+ name = "alien clothing"
+ desc = "This doesn't look very comfortable."
+ icon_state = "vox-casual-1"
+ item_state = "vox-casual-1"
+
+/obj/item/clothing/under/vox/vox_robes
+ name = "alien robes"
+ desc = "Weird and flowing!"
+ icon_state = "vox-casual-2"
+ item_state = "vox-casual-2"
+
+/obj/item/clothing/under/vox/vox_casual
+ name = "alien jumpsuit"
+ desc = "These loose clothes are optimized for the labors of the lower castes onboard the arkships. Large openings in the top allow for breathability while the pants are durable yet flexible enough to not restrict movement."
+ icon_state = "vox-jumpsuit"
+ item_state = "vox-jumpsuit"
diff --git a/yogstation/code/modules/food_and_drinks/food/recipes/tablecraft/recipes_meat.dm b/yogstation/code/modules/food_and_drinks/food/recipes/tablecraft/recipes_meat.dm
new file mode 100644
index 0000000000000..c1ee2c11344e1
--- /dev/null
+++ b/yogstation/code/modules/food_and_drinks/food/recipes/tablecraft/recipes_meat.dm
@@ -0,0 +1,8 @@
+/datum/crafting_recipe/food/fried_vox
+ name = "Kentucky Fried Vox"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/vox = 1,
+ /obj/item/stack/sheet/animalhide/vox = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/fried_vox
+ category = CAT_MEAT
diff --git a/yogstation/code/modules/food_and_drinks/food/snacks/meat.dm b/yogstation/code/modules/food_and_drinks/food/snacks/meat.dm
index fd94bd15add46..18cede39ff294 100644
--- a/yogstation/code/modules/food_and_drinks/food/snacks/meat.dm
+++ b/yogstation/code/modules/food_and_drinks/food/snacks/meat.dm
@@ -3,4 +3,11 @@
desc = "Damn dirty steak."
filling_color = "#FF0000"
tastes = list("meat" = 4, "fur" = 1)
- foodtype = MEAT | RAW
\ No newline at end of file
+ foodtype = MEAT | RAW
+
+/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/vox
+ icon_state = "voxmeat"
+ desc = "Surprisingly, it doesn't actually taste like chicken."
+ filling_color = COLOR_BLOOD_VOX
+ tastes = list("skrek" = 1)
+ foodtype = MEAT | RAW | TOXIC
diff --git a/yogstation/code/modules/food_and_drinks/food/snacks_meat.dm b/yogstation/code/modules/food_and_drinks/food/snacks_meat.dm
new file mode 100644
index 0000000000000..edf48bd61ff9b
--- /dev/null
+++ b/yogstation/code/modules/food_and_drinks/food/snacks_meat.dm
@@ -0,0 +1,7 @@
+/obj/item/reagent_containers/food/snacks/fried_vox
+ name = "Kentucky Fried Vox"
+ desc = "11 herbs and spices."
+ icon_state = "fried_vox"
+ trash = /obj/item/trash/fried_vox
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/protein = 5)
+ tastes = list("quills" = 1, "the Shoal" = 1)
diff --git a/yogstation/code/modules/language/language_holder.dm b/yogstation/code/modules/language/language_holder.dm
new file mode 100644
index 0000000000000..df4e5f7e51fb6
--- /dev/null
+++ b/yogstation/code/modules/language/language_holder.dm
@@ -0,0 +1,5 @@
+/datum/language_holder/vox
+ understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/vox = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/vox = list(LANGUAGE_ATOM))
diff --git a/yogstation/code/modules/language/voxpidgin.dm b/yogstation/code/modules/language/voxpidgin.dm
new file mode 100644
index 0000000000000..353c94f8a99b0
--- /dev/null
+++ b/yogstation/code/modules/language/voxpidgin.dm
@@ -0,0 +1,11 @@
+/datum/language/vox
+ name = "Vox-pidgin"
+ desc = "The common tongue of the various Vox ships making up the Shoal. It sounds like chaotic shrieking to everyone else."
+ speech_verb = "shrieks"
+ ask_verb = "creels"
+ exclaim_verb = "loudly skrees"
+ key = "v"
+ syllables = list("ti","ti","ti","hi","hi","ki","ki","ki","ki","ya","ta","ha","ka","ya","yi","chi","cha","kah", \
+ "SKRE","AHK","EHK","RAWK","KRA","AAA","EEE","KI","II","KRI","KA")
+ default_priority = 90
+ icon_state = "voxpidgin"
diff --git a/yogstation/code/modules/mob/dead/new_player/sprite_accessories.dm b/yogstation/code/modules/mob/dead/new_player/sprite_accessories.dm
index b6c3742e18d4f..8b4a7c4825887 100644
--- a/yogstation/code/modules/mob/dead/new_player/sprite_accessories.dm
+++ b/yogstation/code/modules/mob/dead/new_player/sprite_accessories.dm
@@ -1,5 +1,303 @@
+/datum/sprite_accessory
+ var/color_blend_mode = COLOR_BLEND_MULTIPLY
+ ///the body slots outside of the main slot this accessory exists in, so we can draw to those spots seperately
+ var/list/body_slots = list()
+ /// the list of external organs covered
+ var/list/external_slots = list()
+ var/list/sprite_sheets = list() //For accessories common across species but need to use 'fitted' sprites (like underwear). e.g. list(SPECIES_VOX = 'icons/mob/species/vox/iconfile.dmi')
+
+/datum/sprite_accessory/undershirt
+ sprite_sheets = list(SPECIES_VOX = 'icons/mob/clothing/species/vox/undershirt.dmi')
+
/datum/sprite_accessory/undershirt/goose
name = "Shirt (Vomit Goose)"
icon_state = "vomitgooseshirt"
icon = 'yogstation/icons/mob/clothing/sprite_accessories/undershirt.dmi'
gender = NEUTER
+
+/datum/sprite_accessory/underwear
+ sprite_sheets = list(SPECIES_VOX = 'icons/mob/clothing/species/vox/underwear.dmi')
+
+/datum/sprite_accessory/socks
+ sprite_sheets = list(SPECIES_VOX = 'icons/mob/clothing/species/vox/socks.dmi')
+
+// Vox Accessories
+
+/datum/sprite_accessory/vox_quills
+ icon = 'icons/mob/species/vox/quills.dmi'
+ color_src = HAIR
+ color_blend_mode = COLOR_BLEND_ADD
+
+/datum/sprite_accessory/vox_quills/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/vox_quills/crested
+ name = "Crested"
+ icon_state = "crested"
+
+/datum/sprite_accessory/vox_quills/emperor
+ name = "Emperor"
+ icon_state = "emperor"
+
+/datum/sprite_accessory/vox_quills/keel
+ name = "Keel"
+ icon_state = "keel"
+
+/datum/sprite_accessory/vox_quills/keet
+ name = "Keet"
+ icon_state = "keet"
+
+/datum/sprite_accessory/vox_quills/short
+ name = "Short"
+ icon_state = "short"
+
+/datum/sprite_accessory/vox_quills/tiel
+ name = "Tiel"
+ icon_state = "tiel"
+
+/datum/sprite_accessory/vox_quills/kingly
+ name = "Kingly"
+ icon_state = "kingly"
+
+/datum/sprite_accessory/vox_quills/afro
+ name = "Fluffy"
+ icon_state = "afro"
+
+/datum/sprite_accessory/vox_quills/yasuhiro
+ name = "Long"
+ icon_state = "yasuhiro"
+
+/datum/sprite_accessory/vox_quills/razor
+ name = "Razorback"
+ icon_state = "razor"
+
+/datum/sprite_accessory/vox_quills/razor_clipped
+ name = "Clipped Razorback"
+ icon_state = "razor_clipped"
+
+/datum/sprite_accessory/vox_quills/long_braid
+ name = "Long Braided"
+ icon_state = "long_braid"
+
+/datum/sprite_accessory/vox_quills/short_braid
+ name = "Short Braided"
+ icon_state = "short_braid"
+
+/datum/sprite_accessory/vox_quills/mowhawk
+ name = "Mohawk"
+ icon_state = "mohawk"
+
+/datum/sprite_accessory/vox_quills/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/vox_quills/horns
+ name = "Horns"
+ icon_state = "horns"
+
+/datum/sprite_accessory/vox_quills/mange
+ name = "Mange"
+ icon_state = "mange"
+
+/datum/sprite_accessory/vox_quills/ponytail
+ name = "Ponytail"
+ icon_state = "ponytail"
+
+/datum/sprite_accessory/vox_quills/rows
+ name = "Rows"
+ icon_state = "rows"
+
+/datum/sprite_accessory/vox_quills/surf
+ name = "Surf"
+ icon_state = "surf"
+
+/datum/sprite_accessory/vox_quills/flowing
+ name = "Flowing"
+ icon_state = "flowing"
+
+/datum/sprite_accessory/vox_quills/nights
+ name = "Nights"
+ icon_state = "nights"
+
+/datum/sprite_accessory/vox_quills/cropped
+ name = "Cropped"
+ icon_state = "cropped"
+
+/datum/sprite_accessory/vox_facial_quills
+ icon = 'icons/mob/species/vox/facial_quills.dmi'
+ color_src = FACEHAIR
+ color_blend_mode = COLOR_BLEND_ADD
+
+/datum/sprite_accessory/vox_facial_quills/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/vox_facial_quills/colonel
+ name = "Colonel"
+ icon_state = "colonel"
+
+/datum/sprite_accessory/vox_facial_quills/fu
+ name = "Fu"
+ icon_state = "fu"
+
+/datum/sprite_accessory/vox_facial_quills/neck
+ name = "Neck"
+ icon_state = "neck"
+
+/datum/sprite_accessory/vox_facial_quills/beard
+ name = "Beard"
+ icon_state = "beard"
+
+/datum/sprite_accessory/vox_facial_quills/ruff
+ name = "Ruff"
+ icon_state = "ruff"
+
+/datum/sprite_accessory/vox_tails
+ icon = 'icons/mob/species/vox/tails.dmi'
+ color_src = NONE
+
+/datum/sprite_accessory/vox_tails/lime
+ name = "lime"
+ icon_state = "lime"
+
+/datum/sprite_accessory/vox_tails/crimson
+ name = "crimson"
+ icon_state = "crimson"
+
+/datum/sprite_accessory/vox_tails/grey
+ name = "grey"
+ icon_state = "grey"
+
+/datum/sprite_accessory/vox_tails/nebula
+ name = "nebula"
+ icon_state = "nebula"
+
+/datum/sprite_accessory/vox_tails/azure
+ name = "azure"
+ icon_state = "azure"
+
+/datum/sprite_accessory/vox_tails/emerald
+ name = "emerald"
+ icon_state = "emerald"
+
+/datum/sprite_accessory/vox_tails/brown
+ name = "brown"
+ icon_state = "brown"
+
+/datum/sprite_accessory/vox_tails/plum
+ name = "plum"
+ icon_state = "plum"
+
+/datum/sprite_accessory/vox_tails/mossy
+ name = "mossy"
+ icon_state = "mossy"
+
+/datum/sprite_accessory/tails_animated/vox
+ icon = 'icons/mob/species/vox/tails.dmi'
+ color_src = NONE
+
+/datum/sprite_accessory/tails_animated/vox/lime
+ name = "lime"
+ icon_state = "lime"
+
+/datum/sprite_accessory/tails_animated/vox/crimson
+ name = "crimson"
+ icon_state = "crimson"
+
+/datum/sprite_accessory/tails_animated/vox/grey
+ name = "grey"
+ icon_state = "grey"
+
+/datum/sprite_accessory/tails_animated/vox/nebula
+ name = "nebula"
+ icon_state = "nebula"
+
+/datum/sprite_accessory/tails_animated/vox/azure
+ name = "azure"
+ icon_state = "azure"
+
+/datum/sprite_accessory/tails_animated/vox/emerald
+ name = "emerald"
+ icon_state = "emerald"
+
+/datum/sprite_accessory/tails_animated/vox/brown
+ name = "brown"
+ icon_state = "brown"
+
+/datum/sprite_accessory/tails_animated/vox/plum
+ name = "plum"
+ icon_state = "plum"
+
+/datum/sprite_accessory/tails_animated/vox/mossy
+ name = "mossy"
+ icon_state = "mossy"
+
+/datum/sprite_accessory/vox_body_markings
+ icon = 'icons/mob/species/vox/body_markings.dmi'
+ color_blend_mode = COLOR_BLEND_ADD
+
+/datum/sprite_accessory/vox_body_markings/none
+ name = "None"
+
+/datum/sprite_accessory/vox_body_markings/heart
+ name = "Heart"
+ icon_state = "heart"
+ body_slots = list(BODY_ZONE_R_ARM)
+
+/datum/sprite_accessory/vox_body_markings/hive
+ name = "Hive"
+ icon_state = "hive"
+ body_slots = list(BODY_ZONE_CHEST)
+
+/datum/sprite_accessory/vox_body_markings/nightling
+ name = "Nightling"
+ icon_state = "nightling"
+ body_slots = list(BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)
+
+/datum/sprite_accessory/vox_body_markings/tiger_body
+ name = "Tiger-stripe"
+ icon_state = "tiger"
+ body_slots = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+
+/datum/sprite_accessory/vox_tail_markings
+ icon = 'icons/mob/species/vox/tail_markings.dmi'
+ color_src = MUTCOLORS_SECONDARY
+ color_blend_mode = COLOR_BLEND_ADD
+
+/datum/sprite_accessory/vox_tail_markings/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/vox_tail_markings/bands
+ name = "Bands"
+ icon_state = "bands"
+
+/datum/sprite_accessory/vox_tail_markings/tip
+ name = "Tip"
+ icon_state = "tip"
+
+/datum/sprite_accessory/vox_tail_markings/stripe
+ name = "Stripe"
+ icon_state = "stripe"
+
+/datum/sprite_accessory/vox_tail_markings_animated
+ icon = 'icons/mob/species/vox/tail_markings.dmi'
+ color_src = MUTCOLORS_SECONDARY
+ color_blend_mode = COLOR_BLEND_ADD
+
+/datum/sprite_accessory/vox_tail_markings_animated/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/vox_tail_markings_animated/bands
+ name = "Bands"
+ icon_state = "bands"
+
+/datum/sprite_accessory/vox_tail_markings_animated/tip
+ name = "Tip"
+ icon_state = "tip"
+
+/datum/sprite_accessory/vox_tail_markings_animated/stripe
+ name = "Stripe"
+ icon_state = "stripe"
diff --git a/yogstation/code/modules/mob/living/brain/MMI.dm b/yogstation/code/modules/mob/living/brain/MMI.dm
index 6fc408eca8df5..f95595d96224f 100644
--- a/yogstation/code/modules/mob/living/brain/MMI.dm
+++ b/yogstation/code/modules/mob/living/brain/MMI.dm
@@ -6,7 +6,7 @@
brain = newbrain
name = "[initial(name)]: [brain.real_name]"
- to_chat(brainmob, "(If your brain was removed after your death you don't remember how you died, or who killed you. See rule 1.6.") //yogs
+ to_chat(brainmob, "(If your [brain.brain_name] was removed after your death you don't remember how you died, or who killed you. See rule 1.6.") //yogs
update_appearance(UPDATE_ICON)
return
diff --git a/yogstation/code/modules/mob/living/brain/brain_item.dm b/yogstation/code/modules/mob/living/brain/brain_item.dm
index 72746d0c20e7e..f97f3554decb0 100644
--- a/yogstation/code/modules/mob/living/brain/brain_item.dm
+++ b/yogstation/code/modules/mob/living/brain/brain_item.dm
@@ -1,6 +1,27 @@
/obj/item/organ/brain
var/real_name = null //the name we use for MMIs, only used by changelings
+ var/brain_name = "brain"
/obj/item/organ/brain/transfer_identity(mob/living/L)
real_name = L.real_name
- .=..()
\ No newline at end of file
+ .=..()
+
+/obj/item/organ/brain/proc/get_mmi_brain_sprite()
+ return "mmi_brain"
+
+/obj/item/organ/brain/alien/get_mmi_brain_sprite()
+ return "mmi_brain_alien"
+
+/obj/item/organ/brain/vox
+ name = "cortical stack"
+ brain_name = "cortical stack"
+ desc = "A peculiarly advanced bio-electronic device that seems to hold the memories and identity of a Vox."
+ icon_state = "cortical-stack"
+ decay_factor = 0
+
+/obj/item/organ/brain/vox/get_mmi_brain_sprite()
+ return "mmi_cortical_stack"
+
+/obj/item/organ/brain/vox/emp_act(severity)
+ to_chat(owner, span_warning("Your head hurts."))
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, clamp(severity*5, 5, 50))
diff --git a/yogstation/code/modules/mob/living/carbon/human/emote.dm b/yogstation/code/modules/mob/living/carbon/human/emote.dm
new file mode 100644
index 0000000000000..52f0013ab290f
--- /dev/null
+++ b/yogstation/code/modules/mob/living/carbon/human/emote.dm
@@ -0,0 +1,20 @@
+/datum/emote/living/carbon/human/scream
+ message_vox = "shrieks!"
+
+/datum/emote/living/carbon/human/quill
+ key = "quill"
+ key_third_person = "quills"
+ message = "rustles their quills."
+ message_param = "rustles their quills at %t."
+ emote_type = EMOTE_AUDIBLE
+ // Credit to sound-ideas (freesfx.co.uk) for the sound.
+
+/datum/emote/living/carbon/human/quill/get_sound(mob/living/user)
+ return 'sound/effects/voxrustle.ogg'
+
+/datum/emote/living/carbon/human/quill/can_run_emote(mob/user, status_check, intentional)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isvox(user))
+ return FALSE
diff --git a/yogstation/code/modules/mob/living/carbon/human/human.dm b/yogstation/code/modules/mob/living/carbon/human/human.dm
index ea569d177b534..df1047b565ec5 100644
--- a/yogstation/code/modules/mob/living/carbon/human/human.dm
+++ b/yogstation/code/modules/mob/living/carbon/human/human.dm
@@ -16,6 +16,9 @@
/mob/living/carbon/human/species/pod/ivymen //jungleland
race = /datum/species/pod/ivymen
+/mob/living/carbon/human/species/vox
+ race = /datum/species/vox
+
/mob/living/carbon/human/get_blood_state()
if(NOBLOOD in dna.species.species_traits) //Can't have blood problems if your species doesn't have any blood, innit?
return BLOOD_SAFE
diff --git a/yogstation/code/modules/mob/living/carbon/human/species.dm b/yogstation/code/modules/mob/living/carbon/human/species.dm
index ad7bc8cb70482..f33f7a51deed7 100644
--- a/yogstation/code/modules/mob/living/carbon/human/species.dm
+++ b/yogstation/code/modules/mob/living/carbon/human/species.dm
@@ -4,6 +4,13 @@
/datum/species
var/yogs_draw_robot_hair = FALSE //DAMN ROBOTS STEALING OUR HAIR AND AIR
var/yogs_virus_infect_chance = 100
+ var/generate_husk_icon = FALSE
+ var/limb_icon_file
+ var/list/parts_to_husk
+ var/eyes_icon = 'icons/mob/human_face.dmi'
+ var/list/static_part_body_zones
+ var/list/suicide_messages
+ var/list/survival_box_replacements /*= list(items_to_delete= list(), new_items= list())*/
/datum/species/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
. = ..()
@@ -18,3 +25,58 @@
/datum/species/proc/spec_AltClickOn(atom/A,mob/living/carbon/human/H)
return FALSE
+
+/datum/species/proc/return_accessory_layer(layer, datum/sprite_accessory/added_accessory, mob/living/carbon/human/host, passed_color)
+ var/list/return_list = list()
+ var/layertext = mutant_bodyparts_layertext(layer)
+ var/g = (host.gender == FEMALE) ? "f" : "m"
+ for(var/list_item in added_accessory.external_slots)
+ var/can_hidden_render = return_external_render_state(list_item, host)
+ if(!can_hidden_render)
+ continue // we failed the render check just dont bother
+ if(!host.getorganslot(list_item))
+ continue
+ var/mutable_appearance/new_overlay = mutable_appearance(added_accessory.icon, layer = -layer)
+ if(added_accessory.gender_specific)
+ new_overlay.icon_state = "[g]_[list_item]_[added_accessory.icon_state]_[layertext]"
+ else
+ new_overlay.icon_state = "m_[list_item]_[added_accessory.icon_state]_[layertext]"
+ new_overlay.color = passed_color
+ return_list += new_overlay
+
+ for(var/list_item in added_accessory.body_slots)
+ if(!host.get_bodypart(list_item))
+ continue
+ var/mutable_appearance/new_overlay = mutable_appearance(added_accessory.icon, layer = -layer)
+ if(added_accessory.gender_specific)
+ new_overlay.icon_state = "[g]_[list_item]_[added_accessory.icon_state]_[layertext]"
+ else
+ new_overlay.icon_state = "m_[list_item]_[added_accessory.icon_state]_[layertext]"
+ new_overlay.color = passed_color
+ return_list += new_overlay
+
+ return return_list
+
+/proc/return_external_render_state(external_slot, mob/living/carbon/human/human)
+ switch(external_slot)
+ if(ORGAN_SLOT_TAIL)
+ if(human.wear_suit && (human.wear_suit.flags_inv & HIDEJUMPSUIT))
+ return FALSE
+ return TRUE
+
+/datum/species/proc/get_icon_variant(mob/living/carbon/person_to_check)
+ return
+
+/datum/species/proc/get_eyes_static(mob/living/carbon/person_to_check)
+ return
+
+/datum/species/proc/get_special_statics(mob/living/carbon/person_to_check)
+ return list()
+
+/datum/species/proc/survival_box_replacement(mob/living/carbon/human/box_holder, obj/item/storage/box/survival_box, list/soon_deleted_items, list/soon_added_items)
+ for(var/item as anything in soon_deleted_items)
+ var/obj/item/item_to_delete = (locate(item) in survival_box)
+ if(item_to_delete)
+ qdel(item_to_delete)
+ for(var/item as anything in soon_added_items)
+ new item(survival_box)
diff --git a/yogstation/code/modules/mob/living/carbon/human/species_types/vox.dm b/yogstation/code/modules/mob/living/carbon/human/species_types/vox.dm
new file mode 100644
index 0000000000000..7ca6869956d43
--- /dev/null
+++ b/yogstation/code/modules/mob/living/carbon/human/species_types/vox.dm
@@ -0,0 +1,185 @@
+/datum/species/vox
+ name = "Vox"
+ plural_form = "Vox"
+ id = SPECIES_VOX
+ is_dimorphic = FALSE
+ generate_husk_icon = TRUE
+ species_traits = list(EYECOLOR, HAS_TAIL, HAS_FLESH, HAS_BONE, HAIRCOLOR, FACEHAIRCOLOR, MUTCOLORS, MUTCOLORS_SECONDARY) // Robust, but cannot be cloned easily.
+ inherent_traits = list(TRAIT_RESISTCOLD, TRAIT_NOCLONE)
+ mutant_bodyparts = list("vox_quills", "vox_body_markings", "vox_facial_quills", "vox_tail", "vox_tail_markings")
+ default_features = list("vox_quills" = "None", "vox_facial_quills" = "None", "vox_body_markings" = "None", "vox_tail" = "lime", "vox_tail_markings" = "None", "vox_skin_tone" = "lime")
+ attack_verbs = list("scratch", "claw")
+ attack_effect = ATTACK_EFFECT_CLAW
+ attack_sound = 'sound/weapons/slash.ogg'
+ miss_sound = 'sound/weapons/slashmiss.ogg'
+ screamsound = list('sound/voice/vox/shriek1.ogg'/*, 'sound/voice/vox/ashriek.ogg'*/)
+ mutantbrain = /obj/item/organ/brain/vox // Brain damage on EMP
+ mutantheart = /obj/item/organ/heart/vox
+ mutantliver = /obj/item/organ/liver/vox // Liver damage on EMP
+ mutantstomach = /obj/item/organ/stomach/vox // Disgust on EMP
+ mutanttongue = /obj/item/organ/tongue/vox
+ mutantlungs = /obj/item/organ/lungs/vox // Causes them to.. gasp.
+ mutantears = /obj/item/organ/ears/vox // Very brief deafness
+ mutanteyes = /obj/item/organ/eyes/vox // Quick hallucination
+ mutanttail = /obj/item/organ/tail/vox
+ mutantappendix = /obj/item/organ/appendix/vox
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/vox
+ skinned_type = /obj/item/stack/sheet/animalhide/vox
+ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT
+ breathid = "n2"
+ suicide_messages = list(
+ "%%SUICIDER%% is jamming %%P_THEIR%% claws into %%P_THEIR%% eye sockets!",
+ "%%SUICIDER%% is deeply inhaling oxygen!")
+ husk_color = null
+ eyes_icon = 'icons/mob/species/vox/eyes.dmi'
+ icon_husk = 'icons/mob/species/vox/bodyparts.dmi'
+ limb_icon_file = 'icons/mob/species/vox/bodyparts.dmi'
+ static_part_body_zones = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ parts_to_husk = list("vox_tail", "vox_tail_markings", "wagging_vox_tail", "wagging_vox_tail_markings", "vox_body_markings")
+ survival_box_replacements = list(items_to_delete = list(/obj/item/clothing/mask/breath, /obj/item/tank/internals/emergency_oxygen),\
+ new_items = list(/obj/item/tank/internals/emergency_oxygen/nitrogen, /obj/item/clothing/mask/breath/vox))
+ creampie_id = "creampie_vox"
+ exotic_bloodtype = "V"
+ smells_like = "musty quills"
+ liked_food = MEAT | FRIED
+ species_language_holder = /datum/language_holder/vox
+
+/datum/species/vox/get_species_description()
+ return "The Vox are remnants of an ancient race, that originate from arkships. \
+ These bioengineered, reptilian, beaked, and quilled beings have a physiological caste system and follow 'The Inviolate' tenets. \
+ Breathing pure nitrogen, they need specialized masks and tanks for survival outside their arkships. \
+ Their insular nature limits their involvement in broader galactic affairs, maintaining a distinct, yet isolated presence away from other species."
+
+/datum/species/vox/get_species_lore()
+ return list("Vox have no colonies of their own to speak of. Most Vox originate from a location known as the Shoal, a sprawling, labyrinth-like space megalith of debris and asteroids fused together over countless orbits. It is there where their cutthroat and opportunistic behavior stems from.",\
+ "Little is known of Vox history itself, as the Vox do not keep many records beyond personal accomplishments and tales of profit and triumph. They have lived among the Shoal and the stars as long as they or anyone else can remember.",\
+ "It is possible the species went through a dark age before being introduced to the greater galactic community. Those taken by Vox raiders are rumored to be taken back to the Shoal to be sold as slaves. Other rumors speculate those not cut out to be slaves are sold by fleshpeddlers for “extraction”.",\
+ "Many Vox have grown fond of life among the stars, and typically avoid living on planetary colonies. They have a penchant for capitalism and goods peddling, deep space salvage, and sometimes interstellar crime. Many Vox now seek their fortune on the border of civilized space. Some trading with stations and worlds as (questionably legal) merchants, or applying to work for any one of the countless mega-corporations on the frontier. Others profit off crime and marauding, hijacking ships, cargo, and sometimes people to bring back to the Shoal.")
+
+/datum/species/vox/get_butt_sprite()
+ return BUTT_SPRITE_VOX
+
+/datum/species/vox/get_cough_sound(mob/living/carbon/human/vox)
+ return 'sound/voice/vox/shriekcough.ogg'
+
+/datum/species/vox/get_sneeze_sound(mob/living/carbon/human/vox)
+ return 'sound/voice/vox/shrieksneeze.ogg'
+
+/datum/species/vox/random_name(unique)
+ if(unique)
+ return random_unique_vox_name()
+ return capitalize(vox_name())
+
+/datum/species/vox/go_bald(mob/living/carbon/human/vox)
+ if(QDELETED(vox)) //may be called from a timer
+ return
+ vox.dna.features["vox_facial_quills"] = "None"
+ vox.dna.features["vox_quills"] = "None"
+ vox.dna.update_uf_block(DNA_VOX_FACIAL_QUILLS_BLOCK)
+ vox.dna.update_uf_block(DNA_VOX_QUILLS_BLOCK)
+ vox.update_hair()
+
+/datum/species/vox/survival_box_replacement(mob/living/carbon/human/box_holder, obj/item/storage/box/survival_box, list/soon_deleted_items, list/soon_added_items)
+ var/mask_to_replace = /obj/item/clothing/mask/breath/vox
+ if(mask_to_replace in soon_added_items)
+ var/list/possible_masks = list(/obj/item/clothing/mask/breath/vox, /obj/item/clothing/mask/breath/vox/respirator)
+ soon_added_items -= mask_to_replace
+ soon_added_items += pick(possible_masks)
+ ..()
+
+/datum/species/vox/after_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE) // Don't forget your voxygen tank
+ if(!H.can_breathe_mask())
+ var/obj/item/clothing/mask/current_mask = H.get_item_by_slot(ITEM_SLOT_MASK)
+ if(!H.equip_to_slot_if_possible(current_mask, ITEM_SLOT_BACKPACK, disable_warning = TRUE))
+ H.put_in_hands(current_mask)
+ var/obj/item/clothing/mask/vox_mask
+ var/mask_pref = H.client?.prefs?.read_preference(/datum/preference/choiced/vox_mask)
+ if(mask_pref == "Respirator")
+ vox_mask = new /obj/item/clothing/mask/breath/vox/respirator
+ else
+ vox_mask = new /obj/item/clothing/mask/breath/vox
+ H.equip_to_slot_or_del(vox_mask, ITEM_SLOT_MASK)
+ var/obj/item/tank/internals_tank
+ var/tank_pref = H.client?.prefs?.read_preference(/datum/preference/choiced/vox_tank_type)
+ if(tank_pref == "Large")
+ internals_tank = new /obj/item/tank/internals/nitrogen
+ else
+ internals_tank = new /obj/item/tank/internals/emergency_oxygen/vox
+ if(!H.equip_to_appropriate_slot(internals_tank))
+ H.put_in_hands(internals_tank)
+ to_chat(H, span_notice("You are now running on nitrogen internals from [internals_tank]. Your species finds oxygen toxic, so you must breathe pure nitrogen."))
+ H.open_internals(internals_tank)
+
+/datum/species/vox/get_icon_variant(mob/living/carbon/person_to_check)
+ return person_to_check.dna.features["vox_skin_tone"]
+
+/datum/species/vox/get_footprint_sprite()
+ return FOOTPRINT_SPRITE_CLAWS
+
+/datum/species/vox/get_eyes_static(mob/living/carbon/person_to_check)
+ var/list/blue_static_skin_tones = list("crimson", "mossy")
+ if(person_to_check.dna.features["vox_skin_tone"] in blue_static_skin_tones)
+ return "blue"
+ else
+ return "green"
+
+/datum/species/vox/handle_body(mob/living/carbon/human/H)
+ update_skin_tone(H)
+ ..()
+
+/datum/species/vox/proc/update_skin_tone(mob/living/carbon/human/vox, skin_tone)
+ if(!skin_tone)
+ skin_tone = vox.dna.features["vox_skin_tone"]
+ vox.dna.features["vox_skin_tone"] = skin_tone
+ vox.dna.update_uf_block(DNA_VOX_SKIN_TONE_BLOCK)
+ var/obj/item/organ/tail/vox/vox_tail = vox.getorganslot(ORGAN_SLOT_TAIL)
+ if(vox_tail && istype(vox_tail))
+ vox_tail.update_tail_appearance(vox)
+
+/datum/species/vox/get_special_statics(mob/living/carbon/human/vox)
+ return list("mossy")
+
+/datum/species/vox/create_pref_unique_perks()
+ var/list/to_add = list()
+
+ to_add += list(
+ list(
+ SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK,
+ SPECIES_PERK_ICON = "temperature-low",
+ SPECIES_PERK_NAME = "Cold Resistance",
+ SPECIES_PERK_DESC = "Vox hides provide excellent insulation against the coldness of space.",
+ ),
+ list(
+ SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK,
+ SPECIES_PERK_ICON = "heart-circle-check",
+ SPECIES_PERK_NAME = "Imperishable Organs",
+ SPECIES_PERK_DESC = "Vox organs contain advanced cybernetics that prevent them from decaying.",
+ ),
+ list(
+ SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK,
+ SPECIES_PERK_ICON = "bolt",
+ SPECIES_PERK_NAME = "EMP Sensitivity",
+ SPECIES_PERK_DESC = "Due to their organs being partially synthetic, they are susceptible to EMP damage.",
+ ),
+ list(
+ SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK,
+ SPECIES_PERK_ICON = "head-side-mask",
+ SPECIES_PERK_NAME = "Nitrogen Breathing",
+ SPECIES_PERK_DESC = "Oxygen is toxic to Vox, they must breathe pure nitrogen.",
+ ),
+ list(
+ SPECIES_PERK_TYPE = SPECIES_NEGATIVE_PERK,
+ SPECIES_PERK_ICON = "dna",
+ SPECIES_PERK_NAME = "Unclonable",
+ SPECIES_PERK_DESC = "Their peculiar physiology prevents Vox from being cloned.",
+ ),
+ )
+
+ return to_add
+
+/datum/species/vox/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H)
+ if(chem.type == /datum/reagent/gas/oxygen)
+ H.adjustToxLoss(1*REAGENTS_EFFECT_MULTIPLIER)
+ H.reagents.remove_reagent(chem.type, chem.metabolization_rate)
+ return FALSE
+ return ..()
diff --git a/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm b/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 21fd26add6eda..62336c19d1fc8 100644
--- a/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/yogstation/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -12,6 +12,11 @@
race = /datum/species/pod/ivymen
mutationtext = span_danger("The pain subsides. You feel... thorny.")
+/datum/reagent/mutationtoxin/vox
+ name = "Vox Mutation Toxin"
+ race = /datum/species/vox
+ mutationtext = span_danger("The pain subsides. You feel... beaked.")
+
/datum/reagent/cluwnification
name = "Cluwne Tears"
description = "Tears from thousands of cluwnes compressed into a dangerous cluwnification virus."
diff --git a/yogstation/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/yogstation/code/modules/reagents/chemistry/recipes/slime_extracts.dm
index 21de610a504a3..6edc2bf3ca81e 100644
--- a/yogstation/code/modules/reagents/chemistry/recipes/slime_extracts.dm
+++ b/yogstation/code/modules/reagents/chemistry/recipes/slime_extracts.dm
@@ -4,4 +4,12 @@
results = list(/datum/reagent/mutationtoxin/gorilla = 1)
required_reagents = list(/datum/reagent/consumable/milk = 1)
required_other = TRUE
- required_container = /obj/item/slime_extract/green
\ No newline at end of file
+ required_container = /obj/item/slime_extract/green
+
+/datum/chemical_reaction/slime/slimevox
+ name = "Vox Mutation Toxin"
+ id = "voxmuttoxin"
+ results = list(/datum/reagent/mutationtoxin/vox = 1)
+ required_reagents = list(/datum/reagent/gas/nitrogen = 1)
+ required_other = TRUE
+ required_container = /obj/item/slime_extract/green
diff --git a/yogstation/code/modules/reagents/reagent_containers/blood_pack.dm b/yogstation/code/modules/reagents/reagent_containers/blood_pack.dm
new file mode 100644
index 0000000000000..30b21a33d047b
--- /dev/null
+++ b/yogstation/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -0,0 +1,2 @@
+/obj/item/reagent_containers/blood/vox
+ blood_type = "V"
diff --git a/yogstation/code/modules/research/designs/limbgrower_designs.dm b/yogstation/code/modules/research/designs/limbgrower_designs.dm
new file mode 100644
index 0000000000000..f3956b3dc7b0c
--- /dev/null
+++ b/yogstation/code/modules/research/designs/limbgrower_designs.dm
@@ -0,0 +1,49 @@
+/datum/design/vox_tail
+ name = "Vox Tail"
+ id = "voxtail"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/synthflesh = 20)
+ build_path = /obj/item/organ/tail/vox/fake
+ category = list("vox")
+
+/datum/design/vox_tongue
+ name = "Vox Tongue"
+ id = "voxtongue"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/synthflesh = 10)
+ build_path = /obj/item/organ/tongue/vox
+ category = list("vox")
+
+/datum/design/vox_eyes
+ name = "Vox Eyes"
+ id = "voxeyes"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/synthflesh = 10)
+ build_path = /obj/item/organ/eyes/vox
+ category = list("vox")
+
+/datum/design/vox_liver
+ name = "Vox Liver"
+ id = "voxliver"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/synthflesh = 10)
+ build_path = /obj/item/organ/liver/vox
+ category = list("vox")
+
+/datum/design/vox_lungs
+ name = "Vox Lungs"
+ id = "voxlungs"
+ build_type = LIMBGROWER
+ reagents_list = list(/datum/reagent/medicine/synthflesh = 10)
+ build_path = /obj/item/organ/lungs/vox
+ category = list("vox")
+
+/obj/item/disk/design_disk/limbs/vox
+ name = "Vox Limb Design Disk"
+ limb_designs = list(/datum/design/vox_tail, /datum/design/vox_tongue, /datum/design/vox_eyes, /datum/design/vox_liver, /datum/design/vox_lungs)
+
+/datum/design/limb_disk/vox
+ name = "Vox Limb Design Disk"
+ desc = "Contains designs for vox organs for the limbgrower - Vox tail, tongue, eyes, liver, and lungs."
+ id = "limbdesign_vox"
+ build_path = /obj/item/disk/design_disk/limbs/vox
diff --git a/yogstation/code/modules/scripting/Implementations/telecomms_translator.dm b/yogstation/code/modules/scripting/Implementations/telecomms_translator.dm
index a8e16f0520250..64ebda9594e76 100644
--- a/yogstation/code/modules/scripting/Implementations/telecomms_translator.dm
+++ b/yogstation/code/modules/scripting/Implementations/telecomms_translator.dm
@@ -13,6 +13,7 @@
#define MOTH (1<<9)
#define CAT (1<<10)
#define ENGLISH (1<<11)
+#define VOXPIDGIN (1<<12)
///Span classes that players are allowed to set in a radio transmission.
GLOBAL_LIST_INIT(allowed_custom_spans, list(
@@ -146,6 +147,8 @@ GLOBAL_LIST_INIT(allowed_translations, list(
"mothian" = MOTH,
"cat" = CAT,
"english" = ENGLISH,
+ "voxpidgin" = VOXPIDGIN,
+
)))
interpreter.Run() // run the thing
@@ -194,6 +197,8 @@ GLOBAL_LIST_INIT(allowed_translations, list(
oldlangbits = CAT
if(/datum/language/english)
oldlangbits = ENGLISH
+ if(/datum/language/vox)
+ oldlangbits = VOXPIDGIN
// Signal data
var/datum/n_struct/signal/script_signal = new(list(
@@ -336,6 +341,8 @@ GLOBAL_LIST_INIT(allowed_translations, list(
return /datum/language/felinid
if(ENGLISH)
return /datum/language/english
+ if(VOXPIDGIN)
+ return /datum/language/vox
/datum/n_function/default/mem
name = "mem"
@@ -516,3 +523,4 @@ GLOBAL_LIST_INIT(allowed_translations, list(
#undef MOTH
#undef CAT
#undef ENGLISH
+#undef VOXPIDGIN
diff --git a/yogstation/code/modules/surgery/bodypart.dm b/yogstation/code/modules/surgery/bodypart.dm
deleted file mode 100644
index 14ac6280c2fce..0000000000000
--- a/yogstation/code/modules/surgery/bodypart.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/obj/item/bodypart
- var/yogs_draw_robot_hair = FALSE
\ No newline at end of file
diff --git a/yogstation/code/modules/surgery/bodyparts/_bodyparts.dm b/yogstation/code/modules/surgery/bodyparts/_bodyparts.dm
new file mode 100644
index 0000000000000..0bc726993e3c1
--- /dev/null
+++ b/yogstation/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -0,0 +1,5 @@
+/obj/item/bodypart
+ var/yogs_draw_robot_hair = FALSE
+ var/has_static_sprite_part = FALSE
+ var/limb_icon_variant
+ var/limb_icon_file
diff --git a/yogstation/code/modules/surgery/bodyparts/head.dm b/yogstation/code/modules/surgery/bodyparts/head.dm
new file mode 100644
index 0000000000000..f7bea89ccf543
--- /dev/null
+++ b/yogstation/code/modules/surgery/bodyparts/head.dm
@@ -0,0 +1,2 @@
+/obj/item/bodypart/head
+ var/eyes_static
diff --git a/yogstation/code/modules/surgery/organs/appendix.dm b/yogstation/code/modules/surgery/organs/appendix.dm
new file mode 100644
index 0000000000000..04d6e36bc17ad
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/appendix.dm
@@ -0,0 +1,4 @@
+/obj/item/organ/appendix/vox
+ name = "vox appendix"
+ icon_state = "appendix-vox"
+ decay_factor = 0
diff --git a/yogstation/code/modules/surgery/organs/ears.dm b/yogstation/code/modules/surgery/organs/ears.dm
new file mode 100644
index 0000000000000..c34b92c799573
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/ears.dm
@@ -0,0 +1,8 @@
+/obj/item/organ/ears/vox
+ name = "vox ears"
+ icon_state = "ears-vox"
+ desc = "Researchers hypothesize that the titanium reinforced tympanic membrane takes the piercing edge off their own shrieking."
+ decay_factor = 0
+
+/obj/item/organ/ears/vox/emp_act()
+ deaf = 10
diff --git a/yogstation/code/modules/surgery/organs/eyes.dm b/yogstation/code/modules/surgery/organs/eyes.dm
new file mode 100644
index 0000000000000..6c15ab93a4be8
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/eyes.dm
@@ -0,0 +1,7 @@
+/obj/item/organ/eyes/vox
+ name = "vox eyes"
+ icon_state = "eyes-vox"
+ decay_factor = 0
+
+/obj/item/organ/eyes/vox/emp_act()
+ owner.adjust_hallucinations(10 SECONDS)
diff --git a/yogstation/code/modules/surgery/organs/heart.dm b/yogstation/code/modules/surgery/organs/heart.dm
index da98967b4da45..e78fb3ee7e2cd 100644
--- a/yogstation/code/modules/surgery/organs/heart.dm
+++ b/yogstation/code/modules/surgery/organs/heart.dm
@@ -20,3 +20,8 @@
to_chat(owner, span_userdanger("You feel your heart collapse in on itself!"))
Remove(owner) //the heart is made of nanites so without them it just breaks down
qdel(src)
+
+/obj/item/organ/heart/vox
+ name = "vox heart"
+ icon_state = "heart-vox"
+ decay_factor = 0
diff --git a/yogstation/code/modules/surgery/organs/liver.dm b/yogstation/code/modules/surgery/organs/liver.dm
new file mode 100644
index 0000000000000..ad6e152497ae3
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/liver.dm
@@ -0,0 +1,4 @@
+/obj/item/organ/liver/vox
+ name = "vox liver"
+ icon_state = "liver-vox"
+ decay_factor = 0
diff --git a/yogstation/code/modules/surgery/organs/lungs.dm b/yogstation/code/modules/surgery/organs/lungs.dm
index 0260538d8ce5f..1df5ddd6e405d 100644
--- a/yogstation/code/modules/surgery/organs/lungs.dm
+++ b/yogstation/code/modules/surgery/organs/lungs.dm
@@ -61,3 +61,18 @@
heat_level_2_threshold = 600
heat_level_3_threshold = 1100
+/obj/item/organ/lungs/vox
+ name = "vox lungs"
+ icon_state = "lungs-vox"
+ desc = "They're filled with dust...wow."
+ decay_factor = 0
+ breathing_class = BREATH_VOX
+
+/obj/item/organ/lungs/vox/populate_gas_info()
+ ..()
+ gas_max[GAS_O2] = 0.05
+ gas_max -= BREATH_VOX
+ gas_damage[GAS_O2] = list(min = MIN_TOXIC_GAS_DAMAGE, max = MAX_TOXIC_GAS_DAMAGE, damage_type = TOX)
+
+/obj/item/organ/lungs/vox/emp_act()
+ owner.emote("gasp")
diff --git a/yogstation/code/modules/surgery/organs/stomach.dm b/yogstation/code/modules/surgery/organs/stomach.dm
new file mode 100644
index 0000000000000..e52c02acff6f2
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/stomach.dm
@@ -0,0 +1,8 @@
+/obj/item/organ/stomach/vox
+ name = "gizzard"
+ icon_state = "stomach-vox"
+ desc = "Mechanical digestion."
+ decay_factor = 0
+
+/obj/item/organ/stomach/vox/emp_act()
+ owner.adjust_disgust(10)
diff --git a/yogstation/code/modules/surgery/organs/tails.dm b/yogstation/code/modules/surgery/organs/tails.dm
new file mode 100644
index 0000000000000..8e90291f0c8da
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/tails.dm
@@ -0,0 +1,73 @@
+/obj/item/organ/tail/vox
+ name = "vox tail"
+ desc = "A severed vox tail. Somewhere, no doubt, a vox hater is very pleased with themselves."
+ icon = 'icons/mob/species/vox/tails.dmi'
+ icon_state = "m_tail_lime_BEHIND"
+ tail_type = "lime"
+ var/icon_state_text = "m_tail_lime"
+ var/icon/constructed_tail_icon
+ var/tail_markings = "None"
+ var/tail_markings_color
+ var/original_owner
+
+/obj/item/organ/tail/vox/Initialize(mapload)
+ . = ..()
+ set_icon_state()
+
+/obj/item/organ/tail/vox/proc/set_icon_state()
+ icon_state_text = "m_tail_[tail_type]"
+ icon_state = "[icon_state_text]_BEHIND"
+ constructed_tail_icon = icon(initial(icon), icon_state)
+ var/icon/constructed_tail_icon_north = icon(constructed_tail_icon, dir = SOUTH)
+ constructed_tail_icon_north.Turn(180)
+ constructed_tail_icon.Insert(constructed_tail_icon_north, dir = NORTH)
+ if(tail_markings && tail_markings != "None")
+ var/datum/sprite_accessory/vox_tail_markings/vox_markings = GLOB.vox_tail_markings_list[tail_markings]
+ var/vox_markings_state_text = "m_vox_tail_markings_[vox_markings.icon_state]"
+ var/icon/constructed_markings = icon(vox_markings.icon, "[vox_markings_state_text]_BEHIND")
+ var/icon/constructed_markings_north = icon(constructed_markings, dir = SOUTH)
+ constructed_markings_north.Turn(180)
+ constructed_markings.Insert(constructed_markings_north, dir = NORTH)
+ constructed_markings.Blend(tail_markings_color, ICON_ADD)
+ constructed_tail_icon.Blend(constructed_markings, ICON_OVERLAY)
+ icon = constructed_tail_icon
+
+/obj/item/organ/tail/vox/Insert(mob/living/carbon/human/H, special = 0, drop_if_replaced = TRUE)
+ ..()
+ if(istype(H))
+ if(!original_owner)
+ original_owner = H
+ var/default_part = H.dna.species.mutant_bodyparts["vox_tail"]
+ if(!default_part || default_part == "None")
+ if(original_owner != H)
+ H.dna.species.mutant_bodyparts["vox_tail"] = tail_type
+ else
+ tail_type = H.dna.species.mutant_bodyparts["vox_tail"] = H.dna.features["vox_skin_tone"]
+
+ default_part = H.dna.species.mutant_bodyparts["vox_tail_markings"]
+ if(!default_part || default_part == "None")
+ if(original_owner != H)
+ H.dna.species.mutant_bodyparts["vox_tail_markings"] = tail_markings
+ else
+ tail_markings = H.dna.species.mutant_bodyparts["vox_tail_markings"] = H.dna.features["vox_tail_markings"]
+ H.update_body()
+
+/obj/item/organ/tail/vox/Remove(mob/living/carbon/human/H, special = 0)
+ ..()
+ if(istype(H))
+ H.dna.species.mutant_bodyparts -= "vox_tail"
+ H.dna.species.mutant_bodyparts -= "vox_tail_markings"
+ update_tail_appearance(H)
+ set_icon_state()
+ H.update_body()
+
+/obj/item/organ/tail/vox/proc/update_tail_appearance(mob/living/carbon/human/tail_owner)
+ if(original_owner && original_owner != tail_owner)
+ return
+ tail_type = tail_owner.dna.features["vox_skin_tone"]
+ tail_markings = tail_owner.dna.features["vox_tail_markings"]
+ tail_markings_color = tail_owner.dna.features["mcolor_secondary"]
+
+/obj/item/organ/tail/vox/fake
+ name = "fabricated vox tail"
+ desc = "A fabricated severed vox tail. This one's made of synthflesh."
diff --git a/yogstation/code/modules/surgery/organs/tongue.dm b/yogstation/code/modules/surgery/organs/tongue.dm
new file mode 100644
index 0000000000000..e8c6e7a2cdf01
--- /dev/null
+++ b/yogstation/code/modules/surgery/organs/tongue.dm
@@ -0,0 +1,14 @@
+/obj/item/organ/tongue/vox
+ name = "vox tongue"
+ desc = "You almost swear you can hear it shrieking."
+ icon_state = "tongue-vox"
+ taste_sensitivity = 50 // There's not much need for taste when you're a scavenger.
+
+/obj/item/organ/tongue/vox/Initialize(mapload)
+ . = ..()
+ attack_verb += "skree'd"
+
+/obj/item/organ/tongue/vox/handle_speech(datum/source, list/speech_args)
+ ..()
+ if(prob(20))
+ playsound(owner, 'sound/voice/vox/shriek1.ogg', 25, 1, 1)
diff --git a/yogstation/code/modules/vending/gift.dm b/yogstation/code/modules/vending/gift.dm
index a08225e21103b..b7edf63050ef3 100644
--- a/yogstation/code/modules/vending/gift.dm
+++ b/yogstation/code/modules/vending/gift.dm
@@ -39,6 +39,37 @@
/obj/item/toy/plush/foxplushie = 2,
/obj/item/toy/plush/pkplushie = 2,
/obj/item/toy/plush/cdragon = 2,
+ /obj/item/toy/plush/voxplushie = 2,
+ /obj/item/flag/nt = 2,
+ /obj/item/flag/clown = 2,
+ /obj/item/flag/mime = 2,
+ /obj/item/flag/ian = 2,
+ /obj/item/flag/species/slime = 2,
+ /obj/item/flag/species/skrell = 2,
+ /obj/item/flag/species/vox = 2,
+ /obj/item/flag/species/machine = 2,
+ /obj/item/flag/species/diona = 2,
+ /obj/item/flag/species/human = 2,
+ /obj/item/flag/species/greys = 2,
+ /obj/item/flag/species/kidan = 2,
+ /obj/item/flag/species/taj = 2,
+ /obj/item/flag/species/lizard = 2,
+ /obj/item/flag/species/vulp = 2,
+ /obj/item/flag/species/drask = 2,
+ /obj/item/flag/species/plasma = 2,
+ /obj/item/flag/species/moth = 2,
+ /obj/item/flag/cargo = 2,
+ /obj/item/flag/med = 2,
+ /obj/item/flag/sec = 2,
+ /obj/item/flag/rnd = 2,
+ /obj/item/flag/atmos = 2,
+ /obj/item/flag/command = 2,
+ /obj/item/flag/grey = 2,
+ /obj/item/flag/syndi = 2,
+ /obj/item/flag/wiz = 2,
+ /obj/item/flag/cult = 2,
+ /obj/item/flag/ussp = 2,
+ /obj/item/flag/solgov = 2,
/obj/item/clothing/head/yogs/froghat = 3,
/obj/item/instrument/accordion = 1,
/obj/item/instrument/eguitar = 1,
diff --git a/yogstation/icons/obj/janitor.dmi b/yogstation/icons/obj/janitor.dmi
index 9dde83583ea10..46c2e2d07af9c 100644
Binary files a/yogstation/icons/obj/janitor.dmi and b/yogstation/icons/obj/janitor.dmi differ
diff --git a/yogstation/icons/obj/stack_objects.dmi b/yogstation/icons/obj/stack_objects.dmi
index 972399a583d3b..d1ea574ddcf41 100644
Binary files a/yogstation/icons/obj/stack_objects.dmi and b/yogstation/icons/obj/stack_objects.dmi differ
diff --git a/yogstation/icons/obj/tank.dmi b/yogstation/icons/obj/tank.dmi
index 999bdfbfb5799..25b84b4483456 100644
Binary files a/yogstation/icons/obj/tank.dmi and b/yogstation/icons/obj/tank.dmi differ