Added additional layers to hairstyles (#90275)

## About The Pull Request

Added two new "layers" to hairstyles. The first draws on the normal hair
layer but resists being affected by hair masks unless a piece of
clothing really insists that it must mask it. The second draws on top of
the headwear layer and will ignore all hair masking, but will disappear
entirely if a piece of clothing covers the part of the head that
hairpiece originates from.

Each of these "Layers" (called "hair appendages" in the code) is
associated with a part of the head that it comes off of (left side,
rear, right side, etc.), which pieces of clothing can cover to prevent
those parts from being shown. For example, a Beret will allow a ponytail
to sprout off the back of a person's head and ignore the normal hair
mask, but a winter coat hood covers every part of the head except the
front, so it will hide the ponytail completely.

## Costs

Hairstyles that use this system require some extra icon blending, but
each hairstyle will cache the result of each blend whenever a new hair
mask is applied, so the extra cost only happens once. The cache will
also be used on hairstyles that don't utilize the additional layers, so
we should end up with less total icon blending over the course of a
round than we currently have.

Adds a new sprite layer, OUTER_HAIR_LAYER, to humans.

Changes hair masks from strings specifying an icon state to datums with
an icon state and a bitfield storing which parts of the head the
headware that uses them cover. The singletons for these datums are
stored in the SSaccessories subsystem.

Any hairstyle that uses this system will need 1-2 more icon states in
the human_face.dmi file. I currently have 32 hairstyles marked that
could reasonably use this system, about half of which use both
additional icon states.

## Images

Old system:

![before1](https://github.com/user-attachments/assets/c8e5827b-751f-4809-b7a4-d30a3b2aaa70)

New system:

![after1](https://github.com/user-attachments/assets/b25ca6e3-f5cd-4385-822e-29604a7d39ba)

![after2](https://github.com/user-attachments/assets/0b0c8007-42d5-42bd-a9b4-6bf7a7e1264e)

The new system is currently only implemented on the "Ponytail (High)"
hairstyle for demonstration and testing. I have a branch with the same
update for most similar hairstyles I can push if this is merged.

## Why It's Good For The Game

Hair masks are okay but they look really bad with some hairstyles. This
allows people who really care about their character's look to make their
hairstyles work with their favorite hats.

Potential for fixing #88886

## Changelog

🆑
add: Added a system to create hairstyles with multiple layers to allow
better interaction with hats and helmets
image: Added additional sprite layers to the Ponytail (High) hairstyle
/🆑

---------

Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
This commit is contained in:
Cruix
2025-04-06 12:50:50 -07:00
committed by GitHub
parent 5a5797555b
commit 6a9ee3b1c6
17 changed files with 216 additions and 78 deletions

View File

@@ -20,6 +20,7 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity
var/list/facial_hairstyles_female_list //! stores only hair names
var/list/hair_gradients_list //! stores /datum/sprite_accessory/hair_gradient indexed by name
var/list/facial_hair_gradients_list //! stores /datum/sprite_accessory/facial_hair_gradient indexed by name
var/list/hair_masks_list //! stores /datum/hair_mask indexed by type
//Underwear
var/list/underwear_list //! stores /datum/sprite_accessory/underwear indexed by name
@@ -60,6 +61,7 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity
/datum/controller/subsystem/accessories/PreInit() // this stuff NEEDS to be set up before GLOB for preferences and stuff to work so this must go here. sorry
setup_lists()
init_hair_gradients()
init_hair_masks()
/// Sets up all of the lists for later utilization in the round and building sprites.
/// In an ideal world we could tack everything that just needed `DEFAULT_SPRITE_LIST` into static variables on the top, but due to the initialization order
@@ -120,6 +122,12 @@ SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity
if(gradient.gradient_category & GRADIENT_APPLIES_TO_FACIAL_HAIR)
facial_hair_gradients_list[gradient.name] = gradient
/datum/controller/subsystem/accessories/proc/init_hair_masks()
hair_masks_list = list()
for(var/path in subtypesof(/datum/hair_mask))
var/datum/hair_mask/mask = new path
hair_masks_list[path] = mask
/// This reads the applicable sprite accessory datum's subtypes and adds it to the subsystem's list of sprite accessories.
/// The boolean `add_blank` argument just adds a "None" option to the list of sprite accessories, like if a felinid doesn't want a tail or something, typically good for gated-off things.
/datum/controller/subsystem/accessories/proc/init_sprite_accessory_subtypes(prototype, add_blank = FALSE)