diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 500553e1f28..185d02d69d6 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -4,6 +4,8 @@
//////////////////////////
/proc/makeDatumRefLists()
+ //horns
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, horn_styles_list)
//hair
init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, hair_styles_list, hair_styles_male_list, hair_styles_female_list)
//facial hair
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index 516ae5630f9..4887c4d12f7 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -1,4 +1,6 @@
//Preferences stuff
+ //Hornstyles
+var/global/list/horn_styles_list = list() //stores /datum/sprite_accessory/horns indexed by name
//Hairstyles
var/global/list/hair_styles_list = list() //stores /datum/sprite_accessory/hair indexed by name
var/global/list/hair_styles_male_list = list()
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 7f0e58d3bbb..0dd408a0028 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -179,6 +179,16 @@ proc/get_id_photo(var/mob/living/carbon/human/H)
facial_s.Blend(rgb(H.r_facial, H.g_facial, H.b_facial), ICON_ADD)
eyes_s.Blend(facial_s, ICON_OVERLAY)
+ //Horns
+ if(H.species && H.species.name == "Unathi")
+ var/datum/sprite_accessory/horns = horn_styles_list[H.horns]
+ if(horns)
+ var/icon/horns_s = new/icon("icon" = horns.icon, "icon_state" = "[horns.icon_state]_s")
+ horns_s.Blend(rgb(H.r_skin, H.g_skin, H.b_skin), ICON_ADD)
+ eyes_s.Blend(horns_s, ICON_OVERLAY)
+
+ preview_icon.Blend(eyes_s, ICON_OVERLAY)
+
var/icon/clothes_s = null
switch(H.mind.assigned_role)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index e7987280d48..f907ca9837d 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -111,6 +111,7 @@ datum/preferences
var/socks = "Nude" //socks type
var/backbag = 2 //backpack type
var/h_style = "Bald" //Hair type
+ var/horns = "None" //Horn style
var/r_hair = 0 //Hair color
var/g_hair = 0 //Hair color
var/b_hair = 0 //Hair color
@@ -353,6 +354,10 @@ datum/preferences
dat += "[TextPreview(flavor_text)]...
"
dat += "
"
+ if(species == "Unathi")
+ dat += "
Horns
"
+ dat += "Style: [horns]
"
+
var/hairname = "Hair"
if(species == "Machine")
hairname = "Frame Color"
@@ -1188,6 +1193,19 @@ datum/preferences
if(new_h_style)
h_style = new_h_style
+ if("horns")
+ var/list/valid_hornstyles = list()
+ for(var/hornstyle in horn_styles_list)
+ var/datum/sprite_accessory/H = horn_styles_list[hornstyle]
+ if( !(species in H.species_allowed))
+ continue
+
+ valid_hornstyles[hornstyle] = horn_styles_list[hornstyle]
+
+ var/new_horn_style = input(user, "Choose your character's horn style:", "Character Preference") as null|anything in valid_hornstyles
+ if(new_horn_style)
+ horns = new_horn_style
+
if("body_accessory")
var/list/possible_body_accessories = list()
if(check_rights(R_ADMIN, 1, user))
@@ -1607,6 +1625,8 @@ datum/preferences
character.undershirt = undershirt
character.socks = socks
+ if(character.species.name == "Unathi")
+ character.horns = horns
if(body_accessory)
character.body_accessory = body_accessory_by_name["[body_accessory]"]
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index f1dd7b307f3..8d74bbb3cbd 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -134,6 +134,7 @@
S["skin_red"] >> r_skin
S["skin_green"] >> g_skin
S["skin_blue"] >> b_skin
+ S["horn_style_name"] >> horns
S["hair_style_name"] >> h_style
S["facial_style_name"] >> f_style
S["eyes_red"] >> r_eyes
@@ -197,6 +198,7 @@
r_skin = sanitize_integer(r_skin, 0, 255, initial(r_skin))
g_skin = sanitize_integer(g_skin, 0, 255, initial(g_skin))
b_skin = sanitize_integer(b_skin, 0, 255, initial(b_skin))
+ horns = sanitize_inlist(horns, horn_styles_list, initial(horns))
h_style = sanitize_inlist(h_style, hair_styles_list, initial(h_style))
f_style = sanitize_inlist(f_style, facial_hair_styles_list, initial(f_style))
r_eyes = sanitize_integer(r_eyes, 0, 255, initial(r_eyes))
diff --git a/code/modules/mob/living/carbon/human/body_accessories.dm b/code/modules/mob/living/carbon/human/body_accessories.dm
index 452317e7c4b..f9450c457ee 100644
--- a/code/modules/mob/living/carbon/human/body_accessories.dm
+++ b/code/modules/mob/living/carbon/human/body_accessories.dm
@@ -91,6 +91,8 @@ var/global/list/body_accessory_by_species = list("None" = null)
icon = 'icons/mob/body_accessory.dmi'
animated_icon = 'icons/mob/body_accessory.dmi'
blend_mode = ICON_ADD
+ icon_state = "null"
+ animated_icon_state = "null"
/datum/body_accessory/tail/try_restrictions(var/mob/living/carbon/human/H)
if(!H.wear_suit || !(H.wear_suit.flags_inv & HIDETAIL) && !istype(H.wear_suit, /obj/item/clothing/suit/space))
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 0bc919e4b0c..f2592a653c3 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -1,12 +1,16 @@
/mob/living/carbon/human
hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPCHEM_HUD,IMPTRACK_HUD,SPECIALROLE_HUD,NATIONS_HUD)
+
//Hair colour and style
var/r_hair = 0
var/g_hair = 0
var/b_hair = 0
var/h_style = "Bald"
+ //Horns
+ var/horns = "None"
+
//Facial hair colour and style
var/r_facial = 0
var/g_facial = 0
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index d21aea4bde2..16ec5ace8ad 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -120,16 +120,17 @@ Please contact me on #coderbus IRC. ~Carn x
#define SUIT_STORE_LAYER 13
#define BACK_LAYER 14
#define HAIR_LAYER 15 //TODO: make part of head layer?
-#define FACEMASK_LAYER 16
-#define HEAD_LAYER 17
-#define COLLAR_LAYER 18
-#define HANDCUFF_LAYER 19
-#define LEGCUFF_LAYER 20
-#define L_HAND_LAYER 21
-#define R_HAND_LAYER 22
-#define TARGETED_LAYER 23 //BS12: Layer for the target overlay from weapon targeting system
-#define FIRE_LAYER 24 //If you're on fire
-#define TOTAL_LAYERS 24
+#define HORN_LAYER 16
+#define FACEMASK_LAYER 17
+#define HEAD_LAYER 18
+#define COLLAR_LAYER 19
+#define HANDCUFF_LAYER 20
+#define LEGCUFF_LAYER 21
+#define L_HAND_LAYER 22
+#define R_HAND_LAYER 23
+#define TARGETED_LAYER 24 //BS12: Layer for the target overlay from weapon targeting system
+#define FIRE_LAYER 25 //If you're on fire
+#define TOTAL_LAYERS 25
@@ -350,6 +351,42 @@ var/global/list/damage_icon_parts = list()
//tail
update_tail_layer(0)
+ //horns
+ update_horns(0)
+
+
+//HORN OVERLAY
+/mob/living/carbon/human/proc/update_horns(var/update_icons=1)
+ //Reset our horns
+ overlays_standing[HORN_LAYER] = null
+
+ var/obj/item/organ/external/head/head_organ = get_organ("head")
+ if(!head_organ || head_organ.is_stump() || (head_organ.status & ORGAN_DESTROYED) )
+ if(update_icons) update_icons()
+ return
+
+ //masks and helmets can obscure our horns, unless we're a synthetic
+ if( (head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR)))
+ if(update_icons) update_icons()
+ return
+
+ //base icons
+ var/icon/horns_standing = new /icon('icons/mob/body_accessory.dmi',"horns_none_s")
+
+ if(horns && !(head && (head.flags & BLOCKHEADHAIR) && !(isSynthetic()) && (src.species.name == "Unathi")))
+ var/datum/sprite_accessory/horn_style = horn_styles_list[horns]
+ if(horn_style && horn_style.species_allowed)
+ if(src.species.name in horn_style.species_allowed)
+ var/icon/horns_s = new/icon("icon" = horn_style.icon, "icon_state" = "[horn_style.icon_state]_s")
+ if(horn_style.do_colouration)
+ horns_s.Blend(rgb(r_skin, g_skin, b_skin), ICON_ADD)
+ horns_standing.Blend(horns_s, ICON_OVERLAY)
+ else
+ //warning("Invalid horns for [species.name]: [horns]")
+
+ overlays_standing[HORN_LAYER] = image(horns_standing)
+
+ if(update_icons) update_icons()
//HAIR OVERLAY
@@ -1032,6 +1069,7 @@ var/global/list/damage_icon_parts = list()
#undef BACK_LAYER
#undef HAIR_LAYER
#undef HEAD_LAYER
+#undef HORN_LAYER
#undef COLLAR_LAYER
#undef HANDCUFF_LAYER
#undef LEGCUFF_LAYER
diff --git a/code/modules/mob/new_player/preferences_setup.dm b/code/modules/mob/new_player/preferences_setup.dm
index f1b9b80e9ab..30b9db678a7 100644
--- a/code/modules/mob/new_player/preferences_setup.dm
+++ b/code/modules/mob/new_player/preferences_setup.dm
@@ -263,6 +263,13 @@ datum/preferences
facial_s.Blend(rgb(r_facial, g_facial, b_facial), ICON_ADD)
eyes_s.Blend(facial_s, ICON_OVERLAY)
+ //Horns
+ if(current_species && current_species.name == "Unathi")
+ var/datum/sprite_accessory/horn_style = horn_styles_list[horns]
+ if(horn_style)
+ var/icon/horns_s = new/icon("icon" = horn_style.icon, "icon_state" = "[horn_style.icon_state]_s")
+ horns_s.Blend(rgb(r_skin, g_skin, b_skin), ICON_ADD)
+ eyes_s.Blend(horns_s, ICON_OVERLAY)
var/icon/underwear_s = null
if(underwear && current_species.clothing_flags & HAS_UNDERWEAR)
diff --git a/code/modules/mob/new_player/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories.dm
index 50183e4e506..b857c6c6adb 100644
--- a/code/modules/mob/new_player/sprite_accessories.dm
+++ b/code/modules/mob/new_player/sprite_accessories.dm
@@ -1498,4 +1498,54 @@
name = "Vox Fishnets"
icon_state = "vox_fishnet"
gender = NEUTER
- species_allowed = list("Vox")
\ No newline at end of file
+ species_allowed = list("Vox")
+
+/* HORNS */
+
+/datum/sprite_accessory/horns
+ icon = 'icons/mob/body_accessory.dmi'
+ species_allowed = list("Unathi")
+ icon_state = null
+
+/datum/sprite_accessory/horns/none
+ name = "None"
+ icon_state = "horns_none"
+
+/datum/sprite_accessory/horns/simple
+ name = "Simple"
+ icon_state = "horns_simple"
+
+/datum/sprite_accessory/horns/short
+ name = "Short"
+ icon_state = "horns_short"
+
+/datum/sprite_accessory/horns/curled
+ name = "Curled"
+ icon_state = "horns_curled"
+
+/datum/sprite_accessory/horns/ram
+ name = "Ram"
+ icon_state = "horns_ram"
+
+/* FRILLS */
+
+/datum/sprite_accessory/frills
+ icon = 'icons/mob/body_accessory.dmi'
+ species_allowed = list("Unathi")
+ icon_state = null
+
+/datum/sprite_accessory/frills/none
+ name = "None"
+ icon_state = null
+
+/datum/sprite_accessory/frills/simple
+ name = "Simple"
+ icon_state = "simple"
+
+/datum/sprite_accessory/frills/short
+ name = "Short"
+ icon_state = "short"
+
+/datum/sprite_accessory/frills/aquatic
+ name = "Aquatic"
+ icon_state = "aqua"
diff --git a/icons/mob/body_accessory.dmi b/icons/mob/body_accessory.dmi
index aafc841e2b5..e18ce310a18 100644
Binary files a/icons/mob/body_accessory.dmi and b/icons/mob/body_accessory.dmi differ