From 38b0939ad423bcc8a3da34c8f046f302a41e0b09 Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Sun, 27 Oct 2024 08:38:03 -0700 Subject: [PATCH] [MIRROR] Adds a second ear slot. (#9329) Co-authored-by: silicons <2003111+silicons@users.noreply.github.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- code/__defines/dna.dm | 78 +++++++++++-------- code/game/dna/dna2.dm | 22 +++++- code/game/dna/dna2_helpers.dm | 14 ++++ .../preference_setup/general/03_body.dm | 64 +++++++++++++++ code/modules/mob/dead/corpse.dm | 9 +++ .../mob/living/carbon/human/human_defines.dm | 8 +- .../carbon/human/species/lleill/hanner.dm | 1 + .../carbon/human/species/lleill/lleill.dm | 1 + .../human/species/species_shapeshift_vr.dm | 31 ++++++++ .../human/species/station/prometheans_vr.dm | 1 + .../station/protean_vr/protean_species.dm | 3 +- .../human/species/station/replicant_crew.dm | 1 + .../human/species/virtual_reality/avatar.dm | 1 + .../mob/living/carbon/human/update_icons.dm | 31 +++++++- .../mob/new_player/sprite_accessories.dm | 14 ++++ .../mob/new_player/sprite_accessories_ear.dm | 12 +++ code/modules/resleeving/designer.dm | 21 ++++- .../tgui/modules/appearance_changer.dm | 36 +++++++++ .../vore/fluffstuff/custom_clothes_vr.dm | 4 +- code/modules/vore/persist/persist_vr.dm | 5 ++ .../AppearanceChangerBody.tsx | 63 ++++++++++----- .../AppearanceChangerDetails.tsx | 17 +++- .../interfaces/AppearanceChanger/types.ts | 9 +++ .../tgui/interfaces/BodyDesigner/types.ts | 5 +- 24 files changed, 385 insertions(+), 66 deletions(-) diff --git a/code/__defines/dna.dm b/code/__defines/dna.dm index ecf85abcd9..10fe4d729b 100644 --- a/code/__defines/dna.dm +++ b/code/__defines/dna.dm @@ -99,37 +99,53 @@ var/SMALLSIZEBLOCK = 0 #define DNA_UI_BEARD_STYLE 15 #define DNA_UI_HAIR_STYLE 16 #define DNA_UI_EAR_STYLE 17 // VOREStation snippet. -#define DNA_UI_TAIL_STYLE 18 -#define DNA_UI_PLAYERSCALE 19 -#define DNA_UI_TAIL_R 20 -#define DNA_UI_TAIL_G 21 -#define DNA_UI_TAIL_B 22 -#define DNA_UI_TAIL2_R 23 -#define DNA_UI_TAIL2_G 24 -#define DNA_UI_TAIL2_B 25 -#define DNA_UI_TAIL3_R 26 -#define DNA_UI_TAIL3_G 27 -#define DNA_UI_TAIL3_B 28 -#define DNA_UI_EARS_R 29 -#define DNA_UI_EARS_G 30 -#define DNA_UI_EARS_B 31 -#define DNA_UI_EARS2_R 32 -#define DNA_UI_EARS2_G 33 -#define DNA_UI_EARS2_B 34 -#define DNA_UI_EARS3_R 35 -#define DNA_UI_EARS3_G 36 -#define DNA_UI_EARS3_B 37 -#define DNA_UI_WING_STYLE 38 -#define DNA_UI_WING_R 39 -#define DNA_UI_WING_G 40 -#define DNA_UI_WING_B 41 -#define DNA_UI_WING2_R 42 -#define DNA_UI_WING2_G 43 -#define DNA_UI_WING2_B 44 -#define DNA_UI_WING3_R 45 -#define DNA_UI_WING3_G 46 -#define DNA_UI_WING3_B 47 // VOREStation snippet end. -#define DNA_UI_LENGTH 47 // VOREStation Edit - Needs to match the highest number above. +#define DNA_UI_EAR_SECONDARY_STYLE 18 // VOREStation snippet. +#define DNA_UI_TAIL_STYLE 19 +#define DNA_UI_PLAYERSCALE 20 +#define DNA_UI_TAIL_R 21 +#define DNA_UI_TAIL_G 22 +#define DNA_UI_TAIL_B 23 +#define DNA_UI_TAIL2_R 24 +#define DNA_UI_TAIL2_G 25 +#define DNA_UI_TAIL2_B 26 +#define DNA_UI_TAIL3_R 27 +#define DNA_UI_TAIL3_G 28 +#define DNA_UI_TAIL3_B 29 + +#define DNA_UI_EARS_R 30 +#define DNA_UI_EARS_G 31 +#define DNA_UI_EARS_B 32 +#define DNA_UI_EARS2_R 33 +#define DNA_UI_EARS2_G 34 +#define DNA_UI_EARS2_B 35 +#define DNA_UI_EARS3_R 36 +#define DNA_UI_EARS3_G 37 +#define DNA_UI_EARS3_B 38 + +#define DNA_UI_EARS_SECONDARY_START 39 +#define DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT 3 + +#define DNA_UI_EARS_SECONDARY_R 39 +#define DNA_UI_EARS_SECONDARY_G 40 +#define DNA_UI_EARS_SECONDARY_B 41 +#define DNA_UI_EARS_SECONDARY2_R 42 +#define DNA_UI_EARS_SECONDARY2_G 43 +#define DNA_UI_EARS_SECONDARY2_B 44 +#define DNA_UI_EARS_SECONDARY3_R 45 +#define DNA_UI_EARS_SECONDARY3_G 46 +#define DNA_UI_EARS_SECONDARY3_B 47 + +#define DNA_UI_WING_STYLE 48 +#define DNA_UI_WING_R 49 +#define DNA_UI_WING_G 50 +#define DNA_UI_WING_B 51 +#define DNA_UI_WING2_R 52 +#define DNA_UI_WING2_G 53 +#define DNA_UI_WING2_B 54 +#define DNA_UI_WING3_R 55 +#define DNA_UI_WING3_G 56 +#define DNA_UI_WING3_B 57 // VOREStation snippet end. +#define DNA_UI_LENGTH 57 // VOREStation Edit - Needs to match the highest number above. #define DNA_SE_LENGTH 49 // VOREStation Edit (original was UI+11) diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm index c7d9ada5f3..f8c2f5f8ab 100644 --- a/code/game/dna/dna2.dm +++ b/code/game/dna/dna2.dm @@ -139,6 +139,10 @@ var/global/list/datum/dna/gene/dna_genes[0] if(character.ear_style) ear_style = ear_styles_list.Find(character.ear_style.type) + var/ear_secondary_style = 0 + if(character.ear_secondary_style) + ear_secondary_style = ear_styles_list.Find(character.ear_secondary_style.type) + // Demi Tails var/tail_style = 0 if(character.tail_style) @@ -170,10 +174,11 @@ var/global/list/datum/dna/gene/dna_genes[0] src.digitigrade = character.digitigrade // +1 to account for the none-of-the-above possibility - SetUIValueRange(DNA_UI_EAR_STYLE, ear_style + 1, ear_styles_list.len + 1, 1) - SetUIValueRange(DNA_UI_TAIL_STYLE, tail_style + 1, tail_styles_list.len + 1, 1) - SetUIValueRange(DNA_UI_PLAYERSCALE, size_multiplier, player_sizes_list.len, 1) - SetUIValueRange(DNA_UI_WING_STYLE, wing_style + 1, wing_styles_list.len + 1, 1) + SetUIValueRange(DNA_UI_EAR_STYLE, ear_style + 1, ear_styles_list.len + 1, 1) + SetUIValueRange(DNA_UI_EAR_SECONDARY_STYLE, ear_secondary_style + 1, ear_styles_list.len + 1, 1) + SetUIValueRange(DNA_UI_TAIL_STYLE, tail_style + 1, tail_styles_list.len + 1, 1) + SetUIValueRange(DNA_UI_PLAYERSCALE, size_multiplier, player_sizes_list.len, 1) + SetUIValueRange(DNA_UI_WING_STYLE, wing_style + 1, wing_styles_list.len + 1, 1) SetUIValueRange(DNA_UI_TAIL_R, character.r_tail, 255, 1) SetUIValueRange(DNA_UI_TAIL_G, character.g_tail, 255, 1) @@ -211,6 +216,15 @@ var/global/list/datum/dna/gene/dna_genes[0] SetUIValueRange(DNA_UI_EARS3_G, character.g_ears3, 255, 1) SetUIValueRange(DNA_UI_EARS3_B, character.b_ears3, 255, 1) + for(var/channel in 1 to DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) + var/offset = DNA_UI_EARS_SECONDARY_START + (channel - 1) * 3 + var/list/read_rgb = ReadRGB(LAZYACCESS(character.ear_secondary_colors, channel) || "#ffffff") + var/red = read_rgb[1] + var/green = read_rgb[2] + var/blue = read_rgb[3] + SetUIValueRange(offset, red, 255, 1) + SetUIValueRange(offset + 1, green, 255, 1) + SetUIValueRange(offset + 2, blue, 255, 1) // VORE Station Edit End SetUIValueRange(DNA_UI_HAIR_R, character.r_hair, 255, 1) diff --git a/code/game/dna/dna2_helpers.dm b/code/game/dna/dna2_helpers.dm index 9b57cd87e1..e7b68fdfe5 100644 --- a/code/game/dna/dna2_helpers.dm +++ b/code/game/dna/dna2_helpers.dm @@ -183,6 +183,11 @@ H.ear_style = null else if((0 < ears) && (ears <= ear_styles_list.len)) H.ear_style = ear_styles_list[ear_styles_list[ears]] + var/ears_secondary = dna.GetUIValueRange(DNA_UI_EAR_SECONDARY_STYLE, ear_styles_list.len + 1) - 1 + if(ears_secondary < 1) + H.ear_secondary_style = null + else if((0 < ears_secondary) && (ears_secondary <= ear_styles_list.len)) + H.ear_secondary_style = ear_styles_list[ear_styles_list[ears_secondary]] // Ear Color H.r_ears = dna.GetUIValueRange(DNA_UI_EARS_R, 255) @@ -195,6 +200,15 @@ H.g_ears3 = dna.GetUIValueRange(DNA_UI_EARS3_G, 255) H.b_ears3 = dna.GetUIValueRange(DNA_UI_EARS3_B, 255) + LAZYINITLIST(H.ear_secondary_colors) + H.ear_secondary_colors.len = max(length(H.ear_secondary_colors), DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) + for(var/channel in 1 to DNA_UI_EARS_SECONDARY_COLOR_CHANNEL_COUNT) + var/offset = DNA_UI_EARS_SECONDARY_START + (channel - 1) * 3 + H.ear_secondary_colors[channel] = rgb( + dna.GetUIValueRange(offset, 255), + dna.GetUIValueRange(offset + 1, 255), + dna.GetUIValueRange(offset + 2, 255), + ) //Tail var/tail = dna.GetUIValueRange(DNA_UI_TAIL_STYLE, tail_styles_list.len + 1) - 1 diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index 4bf1308aef..615bbc69e8 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -17,6 +17,15 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O var/r_ears3 = 30 // Ear tertiary color. var/g_ears3 = 30 // Ear tertiary color var/b_ears3 = 30 // Ear tertiary color + + /// The typepath of the character's selected secondary ears. + var/ear_secondary_style + /// The color channels for the character's selected secondary ears + /// + /// * This is a lazy list. Its length, when populated, should but cannot be assumed + /// to be the number of color channels supported by the secondary ear style. + var/list/ear_secondary_colors = list() + var/tail_style // Type of selected tail style var/r_tail = 30 // Tail/Taur color var/g_tail = 30 // Tail/Taur color @@ -27,6 +36,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O var/r_tail3 = 30 // For tertiary overlay. var/g_tail3 = 30 // For tertiary overlay. var/b_tail3 = 30 // For tertiary overlay. + var/wing_style // Type of selected wing style var/r_wing = 30 // Wing color var/g_wing = 30 // Wing color @@ -37,6 +47,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O var/r_wing3 = 30 // Wing tertiary color var/g_wing3 = 30 // Wing tertiary color var/b_wing3 = 30 // Wing tertiary color + var/datum/browser/markings_subwindow = null // Sanitize ear/wing/tail styles @@ -59,6 +70,8 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O // Sanitize for non-existent keys. if(ear_style && !(ear_style in get_available_styles(global.ear_styles_list))) ear_style = null + if(ear_secondary_style && !(ear_secondary_style in get_available_styles(global.ear_styles_list))) + ear_secondary_style = null if(wing_style && !(wing_style in get_available_styles(global.wing_styles_list))) wing_style = null if(tail_style && !(tail_style in get_available_styles(global.tail_styles_list))) @@ -144,6 +157,8 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.r_ears3 = save_data["r_ears3"] pref.g_ears3 = save_data["g_ears3"] pref.b_ears3 = save_data["b_ears3"] + pref.ear_secondary_style = save_data["ear_secondary_style"] + pref.ear_secondary_colors = save_data["ear_secondary_colors"] pref.tail_style = save_data["tail_style"] pref.r_tail = save_data["r_tail"] pref.g_tail = save_data["g_tail"] @@ -221,6 +236,8 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O save_data["r_ears3"] = pref.r_ears3 save_data["g_ears3"] = pref.g_ears3 save_data["b_ears3"] = pref.b_ears3 + save_data["ear_secondary_style"] = pref.ear_secondary_style + save_data["ear_secondary_colors"] = pref.ear_secondary_colors save_data["tail_style"] = pref.tail_style save_data["r_tail"] = pref.r_tail save_data["g_tail"] = pref.g_tail @@ -289,6 +306,14 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.r_ears3 = sanitize_integer(pref.r_ears3, 0, 255, initial(pref.r_ears3)) pref.g_ears3 = sanitize_integer(pref.g_ears3, 0, 255, initial(pref.g_ears3)) pref.b_ears3 = sanitize_integer(pref.b_ears3, 0, 255, initial(pref.b_ears3)) + + // sanitize secondary ears + pref.ear_secondary_colors = SANITIZE_LIST(pref.ear_secondary_colors) + if(length(pref.ear_secondary_colors) > length(GLOB.fancy_sprite_accessory_color_channel_names)) + pref.ear_secondary_colors.len = length(GLOB.fancy_sprite_accessory_color_channel_names) + for(var/i in 1 to length(pref.ear_secondary_colors)) + pref.ear_secondary_colors[i] = sanitize_hexcolor(pref.ear_secondary_colors[i], "#ffffff") + pref.r_tail = sanitize_integer(pref.r_tail, 0, 255, initial(pref.r_tail)) pref.g_tail = sanitize_integer(pref.g_tail, 0, 255, initial(pref.g_tail)) pref.b_tail = sanitize_integer(pref.b_tail, 0, 255, initial(pref.b_tail)) @@ -364,6 +389,10 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O character.b_ears3 = pref.b_ears3 character.g_ears3 = pref.g_ears3 + // apply secondary ears; sanitize again to prevent runtimes in rendering + character.ear_secondary_style = ear_styles[pref.ear_secondary_style] + character.ear_secondary_colors = SANITIZE_LIST(pref.ear_secondary_colors) + var/list/tail_styles = pref.get_available_styles(global.tail_styles_list) character.tail_style = tail_styles[pref.tail_style] character.r_tail = pref.r_tail @@ -655,6 +684,15 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O else . += " Style: Select
" + var/datum/sprite_accessory/ears/ears_secondary = ear_styles[pref.ear_secondary_style] + . += span_bold("Horns") + "
" + if(istype(ears_secondary)) + . += " Style: [ears_secondary.name]
" + for(var/channel in 1 to min(ears_secondary.get_color_channel_count(), length(GLOB.fancy_sprite_accessory_color_channel_names))) + . += "Change [GLOB.fancy_sprite_accessory_color_channel_names[channel]] Color [color_square(hex = LAZYACCESS(pref.ear_secondary_colors, channel) || "#ffffff")]
" + else + . += " Style: Select
" + var/list/tail_styles = pref.get_available_styles(global.tail_styles_list) var/datum/sprite_accessory/tail/tail = tail_styles[pref.tail_style] . += span_bold("Tail") + "
" @@ -1349,6 +1387,32 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.b_ears3 = hex2num(copytext(new_earc3, 6, 8)) return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["ear_secondary_style"]) + var/new_style = tgui_input_list(user, "Select an ear style for this character:", "Character Preference", pref.get_available_styles(global.ear_styles_list), pref.ear_secondary_style) + if(!new_style) + return TOPIC_NOACTION + pref.ear_secondary_style = new_style + return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["ear_secondary_color"]) + var/channel = text2num(href_list["ear_secondary_color"]) + // very important sanity check; this makes sure someone can't crash the server by setting channel to some insanely high value + if(channel > GLOB.fancy_sprite_accessory_color_channel_names.len) + return TOPIC_NOACTION + // this would say 'secondary ears' but you'd get 'choose your character's primary secondary ear colour' which sounds silly + var/new_color = input( + user, + "Choose your character's [lowertext(GLOB.fancy_sprite_accessory_color_channel_names[channel])] ear colour:", + "Secondary Ear Coloration", + LAZYACCESS(pref.ear_secondary_colors, channel) || "#ffffff", + ) as color | null + if(!new_color) + return TOPIC_NOACTION + // ensures color channel list is at least that long + // the upper bound is to have a secondary safety check because list index set is a dangerous call + pref.ear_secondary_colors.len = clamp(length(pref.ear_secondary_colors), channel, length(GLOB.fancy_sprite_accessory_color_channel_names)) + pref.ear_secondary_colors[channel] = new_color + return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["tail_style"]) var/new_tail_style = tgui_input_list(user, "Select a tail style for this character:", "Character Preference", pref.get_available_styles(global.tail_styles_list), pref.tail_style) if(new_tail_style) diff --git a/code/modules/mob/dead/corpse.dm b/code/modules/mob/dead/corpse.dm index 22a4c2276a..f9dfa5aa5c 100644 --- a/code/modules/mob/dead/corpse.dm +++ b/code/modules/mob/dead/corpse.dm @@ -31,6 +31,9 @@ var/list/random_species_list = list(SPECIES_HUMAN,SPECIES_TAJ,SPECIES_UNATHI,SPECIES_SKRELL) var/list/tail_type = null var/list/ear_type = null + /// list(name of ear, color of ear, color of ear, ...). + /// Color is optional, each position after the name is a color channel from 1 to n. + var/list/ear_secondary_type var/list/wing_type = null var/hair = null // CHOMPAdd var/corpsesynthtype = 0 // 0 for organic, 1 for drone, 2 for posibrain @@ -92,6 +95,12 @@ M.h_style = hair M.update_hair() //CHOMPAdd End + // handle secondary ears + if(length(ear_secondary_type) && (ear_secondary_type[1] in global.ear_styles_list)) + M.ear_secondary_style = global.ear_styles_list[ear_secondary_type[1]] + if(length(ear_secondary_type) > 1) + M.ear_secondary_colors = ear_secondary_type.Copy(2, min(length(GLOB.fancy_sprite_accessory_color_channel_names), length(ear_secondary_type)) + 1) + if(wing_type && wing_type.len) if(wing_type[1] in wing_styles_list) M.wing_style = wing_styles_list[wing_type[1]] diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 5b816be294..d182593d1f 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -135,6 +135,12 @@ var/r_ears3 = 30 //Trust me, we could always use more colour. No japes. var/g_ears3 = 30 var/b_ears3 = 30 + + /// secondary ears sprite accessory reference + var/datum/sprite_accessory/ears/ear_secondary_style + /// secondary ears color channels; can be null, or a list of #aabbcc hexcolors + var/list/ear_secondary_colors + var/datum/sprite_accessory/tail/tail_style = null var/r_tail = 30 var/g_tail = 30 @@ -164,4 +170,4 @@ var/block_hud - var/phobias //For holding a list of phobias \ No newline at end of file + var/phobias //For holding a list of phobias diff --git a/code/modules/mob/living/carbon/human/species/lleill/hanner.dm b/code/modules/mob/living/carbon/human/species/lleill/hanner.dm index 8b7e2b98f8..39fb9f749a 100644 --- a/code/modules/mob/living/carbon/human/species/lleill/hanner.dm +++ b/code/modules/mob/living/carbon/human/species/lleill/hanner.dm @@ -87,6 +87,7 @@ /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, /mob/living/carbon/human/proc/shapeshifter_select_eye_colour, /mob/living/proc/set_size, // /mob/living/carbon/human/proc/lleill_contact, diff --git a/code/modules/mob/living/carbon/human/species/lleill/lleill.dm b/code/modules/mob/living/carbon/human/species/lleill/lleill.dm index 90a294322d..23df1e2ca2 100644 --- a/code/modules/mob/living/carbon/human/species/lleill/lleill.dm +++ b/code/modules/mob/living/carbon/human/species/lleill/lleill.dm @@ -82,6 +82,7 @@ /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, /mob/living/proc/set_size, // /mob/living/carbon/human/proc/lleill_invisibility, // /mob/living/carbon/human/proc/lleill_transmute, diff --git a/code/modules/mob/living/carbon/human/species/species_shapeshift_vr.dm b/code/modules/mob/living/carbon/human/species/species_shapeshift_vr.dm index 48291f349e..26cec74f5a 100644 --- a/code/modules/mob/living/carbon/human/species/species_shapeshift_vr.dm +++ b/code/modules/mob/living/carbon/human/species/species_shapeshift_vr.dm @@ -56,6 +56,37 @@ update_hair() //Includes Virgo ears +/mob/living/carbon/human/proc/shapeshifter_select_secondary_ears() + set name = "Select Secondary Ears" + set category = "Abilities" + + if(stat || world.time < last_special) + return + last_special = world.time + 1 SECONDS + + // Construct the list of names allowed for this user. + var/list/pretty_ear_styles = list("Normal" = null) + for(var/path in ear_styles_list) + var/datum/sprite_accessory/ears/instance = ear_styles_list[path] + if((!instance.ckeys_allowed) || (ckey in instance.ckeys_allowed)) + pretty_ear_styles[instance.name] = path + + // Handle style pick + var/new_ear_style = tgui_input_list(src, "Pick some ears!", "Character Preference", pretty_ear_styles) + if(!new_ear_style) + return + ear_secondary_style = ear_styles_list[pretty_ear_styles[new_ear_style]] + + // Handle color picks + var/list/new_colors = list() + for(var/channel in 1 to ear_secondary_style.get_color_channel_count()) + var/channel_name = GLOB.fancy_sprite_accessory_color_channel_names[channel] + var/default = LAZYACCESS(ear_secondary_colors, channel) || "#ffffff" + var/new_color = input(usr, "Pick [channel_name]", "Ear Color ([channel_name])", default) as color | null + new_colors += new_color || default + + update_hair() + /mob/living/carbon/human/proc/shapeshifter_select_tail() set name = "Select Tail" set category = "Abilities.Shapeshift" //CHOMPEdit diff --git a/code/modules/mob/living/carbon/human/species/station/prometheans_vr.dm b/code/modules/mob/living/carbon/human/species/station/prometheans_vr.dm index 12ed13430f..a5a879d5ce 100644 --- a/code/modules/mob/living/carbon/human/species/station/prometheans_vr.dm +++ b/code/modules/mob/living/carbon/human/species/station/prometheans_vr.dm @@ -31,6 +31,7 @@ /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, /mob/living/carbon/human/proc/prommie_blobform, /mob/living/proc/set_size, /mob/living/carbon/human/proc/promethean_select_opaqueness, diff --git a/code/modules/mob/living/carbon/human/species/station/protean_vr/protean_species.dm b/code/modules/mob/living/carbon/human/species/station/protean_vr/protean_species.dm index e582d2b4d7..2825cdf802 100755 --- a/code/modules/mob/living/carbon/human/species/station/protean_vr/protean_species.dm +++ b/code/modules/mob/living/carbon/human/species/station/protean_vr/protean_species.dm @@ -103,7 +103,8 @@ /mob/living/carbon/human/proc/shapeshifter_select_gender, /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, - /mob/living/carbon/human/proc/shapeshifter_select_ears + /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, ) var/global/list/abilities = list() diff --git a/code/modules/mob/living/carbon/human/species/station/replicant_crew.dm b/code/modules/mob/living/carbon/human/species/station/replicant_crew.dm index d5d3ed3c04..c8e7b769c3 100644 --- a/code/modules/mob/living/carbon/human/species/station/replicant_crew.dm +++ b/code/modules/mob/living/carbon/human/species/station/replicant_crew.dm @@ -45,6 +45,7 @@ /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, /mob/living/carbon/human/proc/shapeshifter_select_eye_colour, /mob/living/proc/set_size ) diff --git a/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm b/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm index fcd3f32cca..8e772443a6 100644 --- a/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm +++ b/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm @@ -37,6 +37,7 @@ /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_secondary_ears, /mob/living/proc/set_size, /mob/living/carbon/human/proc/regenerate, /mob/living/carbon/human/proc/promethean_select_opaqueness, diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 90f41aede1..905ba94ee3 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -578,7 +578,8 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //see UpdateDamageIcon() if(ears_s.Height() > face_standing.Height()) // Tol ears face_standing.Crop(1, 1, face_standing.Width(), ears_s.Height()) face_standing.Blend(ears_s, ICON_OVERLAY) - if(ear_style?.em_block) + // todo: these should be considered separately, but it'd take a slight refactor to how sprite acc's are rendered (or atleast ears) + if(ear_style?.em_block || ear_secondary_style?.em_block) em_block_ears = em_block_image_generic(image(ears_s)) var/image/semifinal = image(face_standing, layer = BODY_LAYER+HAIR_LAYER, "pixel_y" = head_organ.head_offset) @@ -1356,11 +1357,13 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //see UpdateDamageIcon() /mob/living/carbon/human/proc/get_ears_overlay() //If you are FBP with ear style and didn't set a custom one var/datum/robolimb/model = isSynthetic() - if(istype(model) && model.includes_ears && !ear_style) + if(istype(model) && model.includes_ears && !ear_style && !ear_secondary_style) var/icon/ears_s = new/icon("icon" = synthetic.icon, "icon_state" = "ears") ears_s.Blend(rgb(src.r_ears, src.g_ears, src.b_ears), species.color_mult ? ICON_MULTIPLY : ICON_ADD) return ears_s + var/icon/rendered + if(ear_style && !(head && (head.flags_inv & BLOCKHEADHAIR))) var/icon/ears_s = new/icon("icon" = ear_style.icon, "icon_state" = ear_style.icon_state) if(ear_style.do_colouration) @@ -1375,9 +1378,29 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //see UpdateDamageIcon() overlay.Blend(rgb(src.r_ears3, src.g_ears3, src.b_ears3), ear_style.color_blend_mode) ears_s.Blend(overlay, ICON_OVERLAY) qdel(overlay) - return ears_s - return null + rendered = ears_s + // todo: this is utterly horrible but i don't think i should be violently refactoring sprite acc rendering in a feature PR ~silicons + if(ear_secondary_style && !(head && (head.flags_inv & BLOCKHEADHAIR))) + var/icon/ears_s = new/icon("icon" = ear_secondary_style.icon, "icon_state" = ear_secondary_style.icon_state) + if(ear_secondary_style.do_colouration) + ears_s.Blend(LAZYACCESS(ear_secondary_colors, 1), ear_secondary_style.color_blend_mode) + if(ear_secondary_style.extra_overlay) + var/icon/overlay = new/icon("icon" = ear_secondary_style.icon, "icon_state" = ear_secondary_style.extra_overlay) + overlay.Blend(LAZYACCESS(ear_secondary_colors, 2), ear_secondary_style.color_blend_mode) + ears_s.Blend(overlay, ICON_OVERLAY) + qdel(overlay) + if(ear_secondary_style.extra_overlay2) //MORE COLOURS IS BETTERER + var/icon/overlay = new/icon("icon" = ear_secondary_style.icon, "icon_state" = ear_secondary_style.extra_overlay2) + overlay.Blend(LAZYACCESS(ear_secondary_colors, 3), ear_secondary_style.color_blend_mode) + ears_s.Blend(overlay, ICON_OVERLAY) + qdel(overlay) + if(!rendered) + rendered = ears_s + else + rendered.Blend(ears_s, ICON_OVERLAY) + + return rendered /mob/living/carbon/human/proc/get_tail_image() //If you are FBP with tail style and didn't set a custom one diff --git a/code/modules/mob/new_player/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories.dm index e008f953aa..ffc6dcd1e0 100644 --- a/code/modules/mob/new_player/sprite_accessories.dm +++ b/code/modules/mob/new_player/sprite_accessories.dm @@ -17,6 +17,14 @@ conversion in savefile.dm */ +/** + * Color channel names; this is used in things like character setup, editors, etc. + * + * * The length of this is also used to sanitize color channel list lengths. This should never be longer than the + * maximum number of color channels possible across all sprite accessories. + */ +GLOBAL_LIST_INIT(fancy_sprite_accessory_color_channel_names, list("Primary", "Secondary", "Tertiary", "Quaternary")) + /datum/sprite_accessory var/icon // the icon file the accessory is located in @@ -44,6 +52,12 @@ var/list/hide_body_parts = list() //Uses organ tag defines. Bodyparts in this list do not have their icons rendered, allowing for more spriter freedom when doing taur/digitigrade stuff. +/** + * Gets the number of color channels we have. + */ +/datum/sprite_accessory/proc/get_color_channel_count() + return do_colouration ? 1 : 0 + /* //////////////////////////// / =--------------------= / diff --git a/code/modules/mob/new_player/sprite_accessories_ear.dm b/code/modules/mob/new_player/sprite_accessories_ear.dm index f2a6a66c1d..75003300b6 100644 --- a/code/modules/mob/new_player/sprite_accessories_ear.dm +++ b/code/modules/mob/new_player/sprite_accessories_ear.dm @@ -18,6 +18,18 @@ //species_allowed = list(SPECIES_EVENT1, SPECIES_EVENT2, SPECIES_EVENT3) //Removing Polaris whitelits, ones we need are defined in our files +/** + * Gets the number of color channels we have. + */ +/datum/sprite_accessory/ears/get_color_channel_count() + if(!do_colouration) + return 0 + . = 1 + if(extra_overlay) + . += 1 + if(extra_overlay2) + . += 1 + /datum/sprite_accessory/ears/shadekin name = "Shadekin Ears, colorable" desc = "" diff --git a/code/modules/resleeving/designer.dm b/code/modules/resleeving/designer.dm index 963394a85a..5f8d56ff8a 100644 --- a/code/modules/resleeving/designer.dm +++ b/code/modules/resleeving/designer.dm @@ -148,6 +148,17 @@ temp["colorHref2"] = "ear_color2" styles["Ears"] = temp + temp = list("styleHref" = "ear_style", "style" = "Normal") + if(mannequin.ear_secondary_style) + temp["style"] = mannequin.ear_secondary_style.name + if(length(mannequin.ear_secondary_colors) >= 1) + temp["color"] = mannequin.ear_secondary_colors[1] + temp["colorHref"] = list("act" = "ear_secondary_color", "channel" = 1) + if(length(mannequin.ear_secondary_colors) >= 2) + temp["color"] = mannequin.ear_secondary_colors[2] + temp["colorHref"] = list("act" = "ear_secondary_color", "channel" = 2) + styles["Horns"] = temp + temp = list("styleHref" = "tail_style", "style" = "Normal") if(mannequin.tail_style) temp["style"] = mannequin.tail_style.name @@ -419,7 +430,15 @@ var/href_list = list() href_list["src"] = "\ref[src]" - href_list["[params["target_href"]]"] = params["target_value"] + var/list/target_href_maybe = params["target_href"] + // convert list-form inputs as needed + if(islist(target_href_maybe)) + href_list[target_href_maybe["act"]] = TRUE + for(var/key in target_href_maybe["params"]) + var/val = target_href_maybe["params"][key] + href_list[key] = "[val]" + else + href_list[target_href_maybe] = params["target_value"] var/datum/category_item/player_setup_item/to_use = (params["target_href"] in use_different_category) ? use_different_category[params["target_href"]] : B var/action = 0 diff --git a/code/modules/tgui/modules/appearance_changer.dm b/code/modules/tgui/modules/appearance_changer.dm index 9eeb4de664..eee72f9b38 100644 --- a/code/modules/tgui/modules/appearance_changer.dm +++ b/code/modules/tgui/modules/appearance_changer.dm @@ -191,6 +191,22 @@ update_dna() changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE) return TRUE + if("ear_secondary") + if(can_change(APPEARANCE_ALL_HAIR)) + var/datum/sprite_accessory/ears/instance = locate(params["ref"]) + if(params["clear"]) + instance = null + if(!istype(instance) && !params["clear"]) + return FALSE + target.ear_secondary_style = instance + if(!islist(target.ear_secondary_colors)) + target.ear_secondary_colors = list() + if(length(target.ear_secondary_colors) < instance.get_color_channel_count()) + target.ear_secondary_colors.len = instance.get_color_channel_count() + target.update_hair() + update_dna() + changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE) + return TRUE if("ears_color") if(can_change(APPEARANCE_HAIR_COLOR)) var/new_hair = input(usr, "Please select ear color.", "Ear Color", rgb(target.r_ears, target.g_ears, target.b_ears)) as color|null @@ -213,6 +229,19 @@ target.update_hair() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 + if("ears_secondary_color") + if(can_change(APPEARANCE_HAIR_COLOR)) + var/channel = params["channel"] + if(channel > length(target.ear_secondary_colors)) + return TRUE + var/existing = LAZYACCESS(target.ear_secondary_colors, channel) || "#ffffff" + var/new_color = input(usr, "Please select ear color.", "2nd Ear Color", existing) as color|null + if(new_color && can_still_topic(usr, state)) + target.ear_secondary_colors[channel] = new_color + update_dna() + target.update_hair() + changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) + return TRUE if("tail") if(can_change(APPEARANCE_ALL_HAIR)) var/datum/sprite_accessory/tail/instance = locate(params["ref"]) @@ -406,6 +435,7 @@ // VOREStation Add - Ears/Tails/Wings data["ear_style"] = target.ear_style + data["ear_secondary_style"] = target.ear_secondary_style?.name data["tail_style"] = target.tail_style data["wing_style"] = target.wing_style var/list/markings_data[0] @@ -434,6 +464,12 @@ // VOREStation Add - Ears/Tails/Wings data["ears_color"] = rgb(target.r_ears, target.g_ears, target.b_ears) data["ears2_color"] = rgb(target.r_ears2, target.g_ears2, target.b_ears2) + + // secondary ear colors + var/list/ear_secondary_color_channels = target.ear_secondary_colors || list() + ear_secondary_color_channels.len = target.ear_secondary_style?.get_color_channel_count() || 0 + data["ear_secondary_colors"] = ear_secondary_color_channels + data["tail_color"] = rgb(target.r_tail, target.g_tail, target.b_tail) data["tail2_color"] = rgb(target.r_tail2, target.g_tail2, target.b_tail2) data["wing_color"] = rgb(target.r_wing, target.g_wing, target.b_wing) diff --git a/code/modules/vore/fluffstuff/custom_clothes_vr.dm b/code/modules/vore/fluffstuff/custom_clothes_vr.dm index 3bd2856644..208fda469f 100644 --- a/code/modules/vore/fluffstuff/custom_clothes_vr.dm +++ b/code/modules/vore/fluffstuff/custom_clothes_vr.dm @@ -668,7 +668,9 @@ /obj/item/clothing/head/fluff/avida/mob_can_equip(var/mob/living/carbon/human/H, slot, disable_warning = 0) if(..()) - if(H.ear_style && (H.ear_style.name == "Bnnuy Ears" || H.ear_style.name == "Bnnuy Ears 2")) //check if wearer's ear sprite is compatible with trimmed icon + var/static/list/allowed_ear_names = list("Bnnuy Ears", "Bnnuy Ears 2") + //check if wearer's ear sprite is compatible with trimmed icon + if((H.ear_style?.name in allowed_ear_names) || (H.ear_secondary_style?.name in allowed_ear_names)) item_state = initial(src.item_state) else //if not, just use a generic icon item_state = "avidahatnoears" diff --git a/code/modules/vore/persist/persist_vr.dm b/code/modules/vore/persist/persist_vr.dm index 3c4850f2de..c0b4b9bc7c 100644 --- a/code/modules/vore/persist/persist_vr.dm +++ b/code/modules/vore/persist/persist_vr.dm @@ -149,6 +149,11 @@ prefs.r_ears3 = character.r_ears3 prefs.g_ears3 = character.g_ears3 prefs.b_ears3 = character.b_ears3 + + // secondary ears + prefs.ear_secondary_style = character.ear_secondary_style?.name + prefs.ear_secondary_colors = character.ear_secondary_colors + prefs.r_tail = character.r_tail prefs.b_tail = character.b_tail prefs.g_tail = character.g_tail diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx index 1991030820..68bb73dfcc 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx @@ -1,7 +1,7 @@ import { sortBy } from 'common/collections'; import { useBackend } from '../../backend'; -import { Button, LabeledList, Section } from '../../components'; +import { Button, LabeledList, Section, Stack } from '../../components'; import { Data, species, styles } from './types'; export const AppearanceChangerSpecies = (props) => { @@ -66,23 +66,50 @@ export const AppearanceChangerEars = (props) => { const { ear_style, ear_styles } = data; return ( -
- - {sortBy(ear_styles, (e: styles) => e.name.toLowerCase()).map((ear) => ( - - ))} -
+ + +
+ + {sortBy(ear_styles, (e: styles) => e.name.toLowerCase()).map( + (ear) => ( + + ), + )} +
+
+ +
+ + {sortBy(ear_styles, (e: styles) => e.name.toLowerCase()).map( + (ear) => ( + + ), + )} +
+
+
); }; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx index c5d2f403b7..e9e3472a18 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx @@ -1,6 +1,6 @@ import { useBackend } from '../../backend'; import { Box, Button, ColorBox, LabeledList, Section } from '../../components'; -import { Data } from './types'; +import { Data, SPRITE_ACCESSORY_COLOR_CHANNEL_NAMES } from './types'; export const AppearanceChangerColors = (props) => { const { act, data } = useBackend(); @@ -56,14 +56,25 @@ export const AppearanceChangerColors = (props) => { - + + {data.ear_secondary_colors.map((color, index) => ( + + ))} diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts index 4d62e13177..aae93386f1 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts @@ -1,5 +1,12 @@ import { BooleanLike } from 'common/react'; +export const SPRITE_ACCESSORY_COLOR_CHANNEL_NAMES = [ + 'Primary', + 'Secondary', + 'Tertiary', + 'Quaternary', +]; + export type Data = { name: string; specimen: string; @@ -39,6 +46,8 @@ export type Data = { wing2_color: string; facial_hair_styles: { facialhairstyle: string }[]; hair_styles: { hairstyle: string }[]; + ear_secondary_style: string; + ear_secondary_colors: string[]; }; type genders = { gender_name: string; gender_key: string }[]; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/types.ts b/tgui/packages/tgui/interfaces/BodyDesigner/types.ts index 42be826e55..56f1eae39d 100644 --- a/tgui/packages/tgui/interfaces/BodyDesigner/types.ts +++ b/tgui/packages/tgui/interfaces/BodyDesigner/types.ts @@ -10,9 +10,9 @@ export type Data = { styleHref: string; style: string; color: string | undefined; - colorHref: string | undefined; + colorHref: string | undefined | { act: string; params: Object }; color2?: string | undefined; - colorHref2?: string | undefined; + colorHref2?: string | undefined | { act: string; params: Object }; }; disk: BooleanLike; diskStored: BooleanLike; @@ -31,6 +31,7 @@ export type activeBodyRecord = { digitigrade: BooleanLike; styles: { Ears: colourableStyle; + Horns: colourableStyle; Tail: colourableStyle; Wing: colourableStyle; Hair: simpleStyle;