diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql
index f73134e6704..eaca618b7d3 100644
--- a/SQL/paradise_schema.sql
+++ b/SQL/paradise_schema.sql
@@ -75,6 +75,10 @@ CREATE TABLE `characters` (
`body_accessory` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`gear` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`autohiss` tinyint(1) NOT NULL,
+ `hair_gradient` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `hair_gradient_offset` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0,0',
+ `hair_gradient_colour` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#000000',
+ `hair_gradient_alpha` tinyint(3) UNSIGNED NOT NULL DEFAULT '255',
PRIMARY KEY (`id`),
KEY `ckey` (`ckey`)
) ENGINE=InnoDB AUTO_INCREMENT=125467 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql
index 16ea512fb08..3c843251572 100644
--- a/SQL/paradise_schema_prefixed.sql
+++ b/SQL/paradise_schema_prefixed.sql
@@ -75,6 +75,10 @@ CREATE TABLE `SS13_characters` (
`body_accessory` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`gear` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`autohiss` tinyint(1) NOT NULL,
+ `hair_gradient` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `hair_gradient_offset` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0,0',
+ `hair_gradient_colour` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#000000',
+ `hair_gradient_alpha` tinyint(3) UNSIGNED NOT NULL DEFAULT '255',
PRIMARY KEY (`id`),
KEY `ckey` (`ckey`)
) ENGINE=InnoDB AUTO_INCREMENT=125467 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -276,13 +280,13 @@ CREATE TABLE `SS13_player` (
`fupdate` smallint(4) DEFAULT '0',
`parallax` tinyint(1) DEFAULT '8',
`byond_date` DATE DEFAULT NULL,
+ `keybindings` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ `keybindings` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`2fa_status` ENUM('DISABLED','ENABLED_IP','ENABLED_ALWAYS') NOT NULL DEFAULT 'DISABLED' COLLATE 'utf8mb4_general_ci',
PRIMARY KEY (`id`),
UNIQUE KEY `ckey` (`ckey`),
KEY `lastseen` (`lastseen`),
- KEY `computerid` (`computerid`),
- KEY `ip` (`ip`),
- KEY `fuid` (`fuid`),
+ KEY `co
KEY `fupdate` (`fupdate`)
) ENGINE=InnoDB AUTO_INCREMENT=135298 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
diff --git a/SQL/updates/24-25.sql b/SQL/updates/24-25.sql
new file mode 100644
index 00000000000..a9dc047f27a
--- /dev/null
+++ b/SQL/updates/24-25.sql
@@ -0,0 +1,6 @@
+# Updates DB from 24 to 25 -dearmochi
+# Adds support for hair gradient
+ALTER TABLE `characters` ADD COLUMN `hair_gradient` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL;
+ALTER TABLE `characters` ADD COLUMN `hair_gradient_offset` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0,0';
+ALTER TABLE `characters` ADD COLUMN `hair_gradient_colour` varchar(7) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '#000000';
+ALTER TABLE `characters` ADD COLUMN `hair_gradient_alpha` tinyint(3) UNSIGNED NOT NULL DEFAULT '255'
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index fc67f448526..00789c063db 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -114,3 +114,7 @@
#define PIPE_COLOR_GREEN "#00ff00"
#define PIPE_COLOR_YELLOW "#ffcc00"
#define PIPE_COLOR_PURPLE "#5c1ec0"
+
+// Color matrix utilities
+#define COLOR_MATRIX_ADD(C) list(COLOR_RED, COLOR_GREEN, COLOR_BLUE, C)
+#define COLOR_MATRIX_OVERLAY(C) list(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK, C)
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index ad182c9f9c4..4a7b6f9dfc9 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -363,7 +363,7 @@
#define INVESTIGATE_BOMB "bombs"
// The SQL version required by this version of the code
-#define SQL_VERSION 24
+#define SQL_VERSION 25
// Vending machine stuff
#define CAT_NORMAL 1
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 282ad53cd06..1193efeb4c0 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -10,6 +10,8 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/head_accessory, GLOB.head_accessory_styles_list)
//hair
init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hair_styles_public_list, GLOB.hair_styles_male_list, GLOB.hair_styles_female_list, GLOB.hair_styles_full_list)
+ //hair gradients
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/hair_gradient, GLOB.hair_gradients_list)
//facial hair
init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hair_styles_list, GLOB.facial_hair_styles_male_list, GLOB.facial_hair_styles_female_list)
//underwear
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index fc18f2d83cd..ad0f11f97f1 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -11,7 +11,7 @@ GLOBAL_LIST_INIT(hair_styles_full_list, list()) //fluff hair styles
GLOBAL_LIST_INIT(facial_hair_styles_list, list()) //stores /datum/sprite_accessory/facial_hair indexed by name
GLOBAL_LIST_INIT(facial_hair_styles_male_list, list())
GLOBAL_LIST_INIT(facial_hair_styles_female_list, list())
-GLOBAL_LIST_INIT(skin_styles_female_list, list()) //unused
+GLOBAL_LIST_EMPTY(hair_gradients_list) //stores /datum/sprite_accessory/hair_gradient indexed by name
//Underwear
GLOBAL_LIST_INIT(underwear_list, list()) //stores /datum/sprite_accessory/underwear indexed by name
GLOBAL_LIST_INIT(underwear_m, list()) //stores only underwear name
diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm
index 12d7bc62237..fcc8757be46 100644
--- a/code/modules/client/preference/preferences.dm
+++ b/code/modules/client/preference/preferences.dm
@@ -210,6 +210,13 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
/// Do we want to force our runechat colour to be white?
var/force_white_runechat = FALSE
+ // Hair gradient
+ var/h_grad_style = "None"
+ var/h_grad_offset_x = 0
+ var/h_grad_offset_y = 0
+ var/h_grad_colour = "#000000"
+ var/h_grad_alpha = 255
+
/datum/preferences/New(client/C)
parent = C
b_type = pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
@@ -333,9 +340,18 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
dat += "Hair: "
dat += "[h_style]"
dat += "Color [color_square(h_colour)]"
+ // Secondary hair color
var/datum/sprite_accessory/temp_hair_style = GLOB.hair_styles_public_list[h_style]
if(temp_hair_style && temp_hair_style.secondary_theme && !temp_hair_style.no_sec_colour)
dat += " Color #2 [color_square(h_sec_colour)]"
+ // Hair gradient
+ dat += "
"
+ dat += "- Gradient:"
+ dat += " [h_grad_style]"
+ dat += " Color [color_square(h_grad_colour)]"
+ dat += " [h_grad_alpha]"
+ dat += "
"
+ dat += "- Gradient Offset: [h_grad_offset_x],[h_grad_offset_y]"
dat += "
"
dat += "Facial Hair: "
@@ -1495,6 +1511,29 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
if(new_h_style)
h_style = new_h_style
+ if("h_grad_style")
+ var/result = input(user, "Choose your character's hair gradient style:", "Character Preference") as null|anything in GLOB.hair_gradients_list
+ if(result)
+ h_grad_style = result
+
+ if("h_grad_offset")
+ var/result = input(user, "Enter your character's hair gradient offset as a comma-separated value (x,y). Example:\n0,0 (no offset)\n5,0 (5 pixels to the right)", "Character Preference") as null|text
+ if(result)
+ var/list/expl = splittext(result, ",")
+ if(length(expl) == 2)
+ h_grad_offset_x = clamp(text2num(expl[1]) || 0, -16, 16)
+ h_grad_offset_y = clamp(text2num(expl[2]) || 0, -16, 16)
+
+ if("h_grad_colour")
+ var/result = input(user, "Choose your character's hair gradient colour:", "Character Preference", h_grad_colour) as color|null
+ if(result)
+ h_grad_colour = result
+
+ if("h_grad_alpha")
+ var/result = input(user, "Choose your character's hair gradient alpha:", "Character Preference", h_grad_alpha) as num|null
+ if(!isnull(result))
+ h_grad_alpha = clamp(result, 0, 255)
+
if("headaccessory")
if(S.bodyflags & HAS_HEAD_ACCESSORY) //Species with head accessories.
var/input = "Choose the colour of your your character's head accessory:"
@@ -2200,6 +2239,12 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
H.f_style = f_style
H.alt_head = alt_head
+
+ H.h_grad_style = h_grad_style
+ H.h_grad_offset_x = h_grad_offset_x
+ H.h_grad_offset_y = h_grad_offset_y
+ H.h_grad_colour = h_grad_colour
+ H.h_grad_alpha = h_grad_alpha
//End of head-specific.
character.skin_colour = s_colour
diff --git a/code/modules/client/preference/preferences_mysql.dm b/code/modules/client/preference/preferences_mysql.dm
index bcb5823af6a..72ad26ed70b 100644
--- a/code/modules/client/preference/preferences_mysql.dm
+++ b/code/modules/client/preference/preferences_mysql.dm
@@ -195,7 +195,11 @@
socks,
body_accessory,
gear,
- autohiss
+ autohiss,
+ hair_gradient,
+ hair_gradient_offset,
+ hair_gradient_colour,
+ hair_gradient_alpha
FROM [format_table_name("characters")] WHERE ckey=:ckey AND slot=:slot"}, list(
"ckey" = C.ckey,
"slot" = slot
@@ -276,6 +280,11 @@
loadout_gear = params2list(query.item[51])
autohiss_mode = text2num(query.item[52])
+ h_grad_style = query.item[53]
+ h_grad_offset_x = query.item[54] // parsed down below
+ h_grad_colour = query.item[55]
+ h_grad_alpha = query.item[56]
+
saved = TRUE
qdel(query)
@@ -331,6 +340,14 @@
socks = sanitize_text(socks, initial(socks))
body_accessory = sanitize_text(body_accessory, initial(body_accessory))
+ // h_grad_style =
+ var/list/expl = splittext(h_grad_offset_x, ",")
+ if(length(expl) == 2)
+ h_grad_offset_x = text2num(expl[1]) || 0
+ h_grad_offset_y = text2num(expl[2]) || 0
+ h_grad_colour = sanitize_hexcolor(h_grad_colour)
+ h_grad_alpha = sanitize_integer(h_grad_alpha, 0, 255, initial(h_grad_alpha))
+
// if(isnull(disabilities)) disabilities = 0
if(!player_alt_titles) player_alt_titles = new()
if(!organ_data) src.organ_data = list()
@@ -421,7 +438,11 @@
socks=:socks,
body_accessory=:body_accessory,
gear=:gearlist,
- autohiss=:autohiss_mode
+ autohiss=:autohiss_mode,
+ hair_gradient=:h_grad_style,
+ hair_gradient_offset=:h_grad_offset,
+ hair_gradient_colour=:h_grad_colour,
+ hair_gradient_alpha=:h_grad_alpha
WHERE ckey=:ckey
AND slot=:slot"}, list(
// OH GOD SO MANY PARAMETERS
@@ -477,6 +498,10 @@
"body_accessory" = (body_accessory ? body_accessory : ""),
"gearlist" = (gearlist ? gearlist : ""),
"autohiss_mode" = autohiss_mode,
+ "h_grad_style" = h_grad_style,
+ "h_grad_offset" = "[h_grad_offset_x],[h_grad_offset_y]",
+ "h_grad_colour" = h_grad_colour,
+ "h_grad_alpha" = h_grad_alpha,
"ckey" = C.ckey,
"slot" = default_slot
)
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index e3ef4648fa4..141c73a0a3c 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -354,49 +354,60 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
else
//warning("Invalid ha_style for [species.name]: [ha_style]")
-
-
-//HAIR OVERLAY
+/**
+ * Generates overlays for the hair layer.
+ */
/mob/living/carbon/human/proc/update_hair()
- //Reset our hair
remove_overlay(HAIR_LAYER)
- var/obj/item/organ/external/head/head_organ = get_organ("head")
- if(!head_organ)
+ var/obj/item/organ/external/head/O = get_organ("head")
+ if(!O)
return
- //masks and helmets can obscure our hair, unless we're a synthetic
- if((head && (head.flags & BLOCKHAIR)) || (wear_mask && (wear_mask.flags & BLOCKHAIR)))
+ if((head?.flags & BLOCKHAIR) || (wear_mask?.flags & BLOCKHAIR))
return
- //base icons
- var/icon/hair_standing = new /icon('icons/mob/human_face.dmi',"bald_s")
- if(head_organ.h_style && !(head && (head.flags & BLOCKHEADHAIR) && !(ismachineperson(src))))
- var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_full_list[head_organ.h_style]
- if(hair_style && hair_style.species_allowed)
- if((head_organ.dna.species.name in hair_style.species_allowed) || (head_organ.dna.species.bodyflags & ALL_RPARTS)) //If the head's species is in the list of allowed species for the hairstyle, or the head's species is one flagged to have bodies comprised wholly of cybernetics...
- var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
- if(istype(head_organ.dna.species, /datum/species/slime)) // I am el worstos
- hair_s.Blend("[skin_colour]A0", ICON_AND)
- else if(hair_style.do_colouration)
- hair_s.Blend(head_organ.hair_colour, ICON_ADD)
+ var/mutable_appearance/MA = new()
+ MA.appearance_flags = KEEP_TOGETHER
+ MA.layer = -HAIR_LAYER
+ if(O.h_style && !(head?.flags & BLOCKHEADHAIR) && !ismachineperson(src))
+ var/datum/sprite_accessory/hair/hair = GLOB.hair_styles_full_list[O.h_style]
+ if(hair?.species_allowed && ((O.dna.species.name in hair.species_allowed) || (O.dna.species.bodyflags & ALL_RPARTS)))
+ // Base hair
+ var/mutable_appearance/img_hair = new()
+ img_hair.icon = hair.icon
+ img_hair.icon_state = "[hair.icon_state]_s"
+ if(istype(O.dna.species, /datum/species/slime))
+ img_hair.color = COLOR_MATRIX_OVERLAY("[skin_colour]A0")
+ else if(hair.do_colouration)
+ img_hair.color = COLOR_MATRIX_ADD(O.hair_colour)
+ MA.overlays += img_hair
- if(hair_style.secondary_theme)
- var/icon/hair_secondary_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_[hair_style.secondary_theme]_s")
- if(!hair_style.no_sec_colour)
- hair_secondary_s.Blend(head_organ.sec_hair_colour, ICON_ADD)
- hair_s.Blend(hair_secondary_s, ICON_OVERLAY)
+ // Gradient
+ var/datum/sprite_accessory/hair_gradient/gradient = GLOB.hair_gradients_list[O.h_grad_style]
+ if(gradient)
+ var/mutable_appearance/img_gradient = new()
+ img_gradient.icon = gradient.icon
+ img_gradient.icon_state = gradient.icon_state
+ img_gradient.alpha = O.h_grad_alpha
+ img_gradient.color = COLOR_MATRIX_OVERLAY(O.h_grad_colour)
+ img_gradient.pixel_x = O.h_grad_offset_x
+ img_gradient.pixel_y = O.h_grad_offset_y
+ img_gradient.blend_mode = BLEND_INSET_OVERLAY
+ MA.overlays += img_gradient
- hair_standing = hair_s //hair_standing.Blend(hair_s, ICON_OVERLAY)
- //Having it this way preserves animations. Useful for IPC screens.
- else
- //warning("Invalid h_style for [species.name]: [h_style]")
- //hair_standing.Blend(debrained_s, ICON_OVERLAY)//how does i overlay for fish?
+ // Secondary style
+ if(hair.secondary_theme)
+ var/mutable_appearance/img_secondary = new()
+ img_secondary.icon = hair.icon
+ img_secondary.icon_state = "[hair.icon_state]_[hair.secondary_theme]_s"
+ if(!hair.no_sec_colour)
+ img_secondary.color = COLOR_MATRIX_ADD(O.sec_hair_colour)
+ MA.overlays += img_secondary
- overlays_standing[HAIR_LAYER] = mutable_appearance(hair_standing, layer = -HAIR_LAYER)
+ overlays_standing[HAIR_LAYER] = MA
apply_overlay(HAIR_LAYER)
-
//FACIAL HAIR OVERLAY
/mob/living/carbon/human/proc/update_fhair()
//Reset our facial hair
diff --git a/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
index 44fa9cfbd3b..96151e62274 100644
--- a/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
+++ b/code/modules/mob/new_player/sprite_accessories/sprite_accessories.dm
@@ -616,3 +616,48 @@
/datum/sprite_accessory/socks/black_fishnet
name = "Black Fishnet"
icon_state = "black_fishnet"
+
+/* HAIR GRADIENT */
+
+/datum/sprite_accessory/hair_gradient
+ icon = 'icons/mob/hair_gradients.dmi'
+
+/datum/sprite_accessory/hair_gradient/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/hair_gradient/fadeup
+ name = "Fade Up"
+ icon_state = "fadeup"
+
+/datum/sprite_accessory/hair_gradient/fadedown
+ name = "Fade Down"
+ icon_state = "fadedown"
+
+/datum/sprite_accessory/hair_gradient/vertical_split
+ name = "Vertical Split"
+ icon_state = "vsplit"
+
+/datum/sprite_accessory/hair_gradient/_split
+ name = "Horizontal Split"
+ icon_state = "bottomflat"
+
+/datum/sprite_accessory/hair_gradient/reflected
+ name = "Reflected"
+ icon_state = "reflected_high"
+
+/datum/sprite_accessory/hair_gradient/reflected_inverse
+ name = "Reflected Inverse"
+ icon_state = "reflected_inverse_high"
+
+/datum/sprite_accessory/hair_gradient/wavy
+ name = "Wavy"
+ icon_state = "wavy"
+
+/datum/sprite_accessory/hair_gradient/long_fade_up
+ name = "Long Fade Up"
+ icon_state = "long_fade_up"
+
+/datum/sprite_accessory/hair_gradient/long_fade_down
+ name = "Long Fade Down"
+ icon_state = "long_fade_down"
diff --git a/code/modules/surgery/organs/subtypes/standard.dm b/code/modules/surgery/organs/subtypes/standard.dm
index 895bcbefd95..f992c31fd77 100644
--- a/code/modules/surgery/organs/subtypes/standard.dm
+++ b/code/modules/surgery/organs/subtypes/standard.dm
@@ -213,6 +213,11 @@
var/hair_colour = "#000000"
var/sec_hair_colour = "#000000"
var/h_style = "Bald"
+ var/h_grad_style = "None"
+ var/h_grad_offset_x = 0
+ var/h_grad_offset_y = 0
+ var/h_grad_colour = "#000000"
+ var/h_grad_alpha = 255
//Head accessory colour and style
var/headacc_colour = "#000000"
diff --git a/config/example/dbconfig.txt b/config/example/dbconfig.txt
index ffceb33e7de..e39bec963c0 100644
--- a/config/example/dbconfig.txt
+++ b/config/example/dbconfig.txt
@@ -18,7 +18,7 @@ FEEDBACK_DATABASE feedback
## This value must be set to the version of the paradise schema in use.
## If this value does not match, the SQL database will not be loaded and an error will be generated.
## Roundstart will be delayed.
-DB_VERSION 24
+DB_VERSION 25
## Prefix to be added to the name of every table, older databases will require this be set to erro_
## If left out defaults to erro_ for legacy reasons, if you want no table prefix, give a blank prefix rather then comment out
diff --git a/icons/mob/hair_gradients.dmi b/icons/mob/hair_gradients.dmi
new file mode 100644
index 00000000000..d457ebf7d14
Binary files /dev/null and b/icons/mob/hair_gradients.dmi differ
diff --git a/tools/ci/dbconfig.txt b/tools/ci/dbconfig.txt
index f3a4b587962..2f1522d8470 100644
--- a/tools/ci/dbconfig.txt
+++ b/tools/ci/dbconfig.txt
@@ -2,7 +2,7 @@
# Dont use it ingame
# Remember to update this when you increase the SQL version! -aa
SQL_ENABLED
-DB_VERSION 24
+DB_VERSION 25
ADDRESS 127.0.0.1
PORT 3306
FEEDBACK_DATABASE feedback