Multicoloured Hair Proof of Concept + Minor Marking Code Tweaks (#44111)

* Add highlight option to longovereye human hairstyle

* undo edit to development.toml

* Revert "undo edit to development.toml"

This reverts commit 67eee3ff774a75df2a8502e663ce074626b4ec73.

* undo edit to development.toml

* Resprite right-facing state

* Try to salvage marking colours when sprite count changes

* Use last existing colour if exists instead of flat white

* First iteration of new hairstyle

* Editing pass on sprites

* Possible fix for vulps?

* Remove debug log

* Add comment explaining additional index offset

* Clarify attribution in meta.json

* Reformat marking colour validation

* Raise error when less sprites than markings colours

* Evil displacement index change

* Comment out error throw for now since that will always fail tests

* Insert displacement layer at index + 1, with comment explaining

* VC Feedback: there are now three indices, removed commented out exception

* Displacement below original

* Add more detail to comment
This commit is contained in:
TriviaSolari 2026-07-13 03:51:51 -04:00 • committed by GitHub
parent 3f6e0f74f7
commit 4474deb959
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 91 additions and 5 deletions

View file

@ -196,6 +196,7 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
ent.Comp.MarkingsDisplacement.TryGetValue(proto.BodyPart, out var displacement);
var numDisplacements = 0;
for (var i = 0; i < proto.Sprites.Count; i++)
{
var sprite = proto.Sprites[i];
@ -208,7 +209,14 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
if (!_sprite.LayerMapTryGet(target, layerId, out _, false))
{
var spriteLayer = _sprite.AddLayer(target, sprite, index + i + 1);
// Having three separate indices and a magic +1 is cursed, but:
// - index refers to the index of the organ the marking is applied to
// - i is the current sprite of the marking that is being applied
// - numDisplacements tracks how many displacements have been applied, and is
// an additional offset to ensure that the order of the base sprites is correct
// after inserting a displacement layer
// - The +1 ensures that markings render on top of the base organ
var spriteLayer = _sprite.AddLayer(target, sprite, index + i + numDisplacements + 1);
_sprite.LayerMapSet(target, layerId, spriteLayer);
_sprite.LayerSetSprite(target, layerId, rsi);
}
@ -219,7 +227,18 @@ public sealed partial class VisualBodySystem : SharedVisualBodySystem
_sprite.LayerSetColor(target, layerId, Color.White);
if (displacement != null && proto.CanBeDisplaced)
_displacement.TryAddDisplacement(displacement, (target, target.Comp), index + i + 1, layerId, out _);
{
_displacement.TryAddDisplacement(
displacement,
(target, target.Comp),
// Similar logic as above, but this makes the displacement layer go below the
// original sprite. So it should be all the displacements, then all the sprite layers on top
index + i + 1,
layerId,
out _
);
numDisplacements++;
}
}
applied.Add(marking);

View file

@ -4,6 +4,7 @@ using System.Linq;
using Content.Shared.Body;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Humanoid.Markings;
@ -147,9 +148,31 @@ public sealed partial class MarkingManager
continue;
}
if (marking.Sprites.Count != markings[i].MarkingColors.Count)
var existingColors = markings[i].MarkingColors;
// If there are less colors than sprites, fill missing spots with the last existing color,
// or white, if there are no existing colors
// This will occur if an existing marking is edited to have additional layers
if (marking.Sprites.Count > existingColors.Count)
{
markings[i] = new Marking(marking.ID, marking.Sprites.Count);
var numMissingColors = marking.Sprites.Count - existingColors.Count;
IEnumerable<Color> missingColors;
if (existingColors.Count == 0)
{
missingColors = Enumerable.Repeat(Color.White, numMissingColors);
}
else
{
missingColors = Enumerable.Repeat(existingColors[^1], numMissingColors);
}
markings[i] = new Marking(marking.ID, existingColors.Concat(missingColors));
}
// If there are more colors than sprites, drop the extras
else if (marking.Sprites.Count < existingColors.Count)
{
markings[i] = new Marking(marking.ID, existingColors.Take(marking.Sprites.Count));
}
}
}

View file

@ -105,6 +105,8 @@ marking-HumanHairLong2 = Long Hair 2
marking-HumanHairLong3 = Long Hair 3
marking-HumanHairLongWithBundles = Long With Bundles
marking-HumanHairLongovereye = Long Over Eye
marking-HumanHairLongovereye-longovereye = Primary
marking-HumanHairLongovereye-longovereye_highlight = Highlight
marking-HumanHairLbangs = Long Bangs
marking-HumanHairLongemo = Long Emo
marking-HumanHairLongfringe = Long Fringe
@ -194,9 +196,15 @@ marking-HumanHairUpdo = Updo
marking-HumanHairVlong = Very Long Hair
marking-HumanHairLongest = Very Long Hair 2
marking-HumanHairLongest2 = Very Long Over Eye
marking-HumanHairLongest2-longest2 = Primary
marking-HumanHairLongest2-longovereye_highlight = Highlight
marking-HumanHairVeryshortovereyealternate = Very Short (Over Eye)
marking-HumanHairVlongfringe = Very Long with Fringe
marking-HumanHairVolaju = Volaju
marking-HumanHairWisp = Wisp
marking-HumanHairLongWithBangs = Long With Bangs
marking-HumanHairOverEyePigtail = Pigtail (Over Eye)
marking-HumanHairTerminal = Terminal
marking-HumanHairTerminal-terminal = Primary
marking-HumanHairTerminal-terminal_highlight = Highlight
marking-HumanHairTerminal-terminal_headband = Headband

View file

@ -628,6 +628,8 @@
sprites:
- sprite: Mobs/Customization/human_hair.rsi
state: longovereye
- sprite: Mobs/Customization/human_hair.rsi
state: longovereye_highlight
- type: marking
id: HumanHairLbangs
bodyPart: Hair
@ -1138,6 +1140,8 @@
sprites:
- sprite: Mobs/Customization/human_hair.rsi
state: longest2
- sprite: Mobs/Customization/human_hair.rsi
state: longovereye_highlight
- type: marking
id: HumanHairVeryshortovereyealternate
bodyPart: Hair
@ -1210,3 +1214,19 @@
sprites:
- sprite: Mobs/Customization/human_hair.rsi
state: overeyepigtail
- type: marking
id: HumanHairTerminal
bodyPart: Hair
coloring:
layers:
terminal_headband:
type:
!type:SimpleColoring
color: "#C3C3C3"
sprites:
- sprite: Mobs/Customization/human_hair.rsi
state: terminal
- sprite: Mobs/Customization/human_hair.rsi
state: terminal_highlight
- sprite: Mobs/Customization/human_hair.rsi
state: terminal_headband

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

View file

@ -4,7 +4,7 @@
"x": 32,
"y": 32
},
"copyright": "Taken from https://github.com/tgstation/tgstation/blob/05ec94e46349c35e29ca91e5e97d0c88ae26ad44/icons/mob/species/human/human_face.dmi ,resprited by Alekshhh, a modified by potato1234x, uneven and tailed is drawn by Ubaser, doublebun_long by Emisse, longbundled and bob5 sprited by github:DreamlyJack(624946166152298517), pulato.png made by DreamlyJack, b.png and b_alt.png modified and sprited by KingFroozy (github), long_with_bangs shaped and longbow by Futuristic (and Pyvik) for SS14, cube and baby by CoolioDudio, overeyepigtail by GovnokradZXC",
"copyright": "Taken from https://github.com/tgstation/tgstation/blob/05ec94e46349c35e29ca91e5e97d0c88ae26ad44/icons/mob/species/human/human_face.dmi ,resprited by Alekshhh, a modified by potato1234x, uneven and tailed is drawn by Ubaser, doublebun_long by Emisse, longbundled and bob5 sprited by github:DreamlyJack(624946166152298517), pulato.png made by DreamlyJack, b.png and b_alt.png modified and sprited by KingFroozy (github), long_with_bangs shaped and longbow by Futuristic (and Pyvik) for SS14, cube and baby by CoolioDudio, overeyepigtail by GovnokradZXC, longovereye_highlight edited by TriviaSolari, terminal, terminal_highlight, and terminal_headband sprited by TriviaSolari",
"license": "CC-BY-SA-3.0",
"states": [
{
@ -459,6 +459,10 @@
"name": "longovereye",
"directions": 4
},
{
"name": "longovereye_highlight",
"directions": 4
},
{
"name": "longsidepart",
"directions": 4
@ -699,6 +703,18 @@
"name": "swept2",
"directions": 4
},
{
"name": "terminal",
"directions": 4
},
{
"name": "terminal_highlight",
"directions": 4
},
{
"name": "terminal_headband",
"directions": 4
},
{
"name": "thinning",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B