From 4474deb959ac032f62ee4e9bc4e5afd40c6e0a4b Mon Sep 17 00:00:00 2001 From: TriviaSolari <154280615+TriviaSolari@users.noreply.github.com> Date: Mon, 13 Jul 2026 03:51:51 -0400 Subject: [PATCH] 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 --- Content.Client/Body/VisualBodySystem.cs | 23 +++++++++++++-- .../Humanoid/Markings/MarkingManager.cs | 27 ++++++++++++++++-- .../Locale/en-US/accessories/human-hair.ftl | 8 ++++++ .../Customization/Markings/human_hair.yml | 20 +++++++++++++ .../human_hair.rsi/longovereye_highlight.png | Bin 0 -> 296 bytes .../Customization/human_hair.rsi/meta.json | 18 +++++++++++- .../Customization/human_hair.rsi/terminal.png | Bin 0 -> 1239 bytes .../human_hair.rsi/terminal_headband.png | Bin 0 -> 166 bytes .../human_hair.rsi/terminal_highlight.png | Bin 0 -> 848 bytes 9 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 Resources/Textures/Mobs/Customization/human_hair.rsi/longovereye_highlight.png create mode 100644 Resources/Textures/Mobs/Customization/human_hair.rsi/terminal.png create mode 100644 Resources/Textures/Mobs/Customization/human_hair.rsi/terminal_headband.png create mode 100644 Resources/Textures/Mobs/Customization/human_hair.rsi/terminal_highlight.png diff --git a/Content.Client/Body/VisualBodySystem.cs b/Content.Client/Body/VisualBodySystem.cs index cf2cbd0c49..ad44317924 100644 --- a/Content.Client/Body/VisualBodySystem.cs +++ b/Content.Client/Body/VisualBodySystem.cs @@ -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); diff --git a/Content.Shared/Humanoid/Markings/MarkingManager.cs b/Content.Shared/Humanoid/Markings/MarkingManager.cs index 96a0f8cf74..6c56fd3191 100644 --- a/Content.Shared/Humanoid/Markings/MarkingManager.cs +++ b/Content.Shared/Humanoid/Markings/MarkingManager.cs @@ -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 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)); } } } diff --git a/Resources/Locale/en-US/accessories/human-hair.ftl b/Resources/Locale/en-US/accessories/human-hair.ftl index b9b103b58a..1b4a7dec8d 100644 --- a/Resources/Locale/en-US/accessories/human-hair.ftl +++ b/Resources/Locale/en-US/accessories/human-hair.ftl @@ -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 diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml index adf2bf0bb7..52db2226df 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml @@ -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 diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/longovereye_highlight.png b/Resources/Textures/Mobs/Customization/human_hair.rsi/longovereye_highlight.png new file mode 100644 index 0000000000000000000000000000000000000000..a19f5a0ede0e1ad1ad3756ef9327507cbab1098e GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMpx`Y} z7srr_TW@b2l6Wz%1hMbW0+mn%RNa@)NTvVh^6(^yhwOw~@_-(+fnN zEqiY>r8w-IrQ|zB7TY3*XXhRz_mqANp4Sl66?J{9-o5JeYqvVHC%m)Sl6(99A7(qg z2U~x?y7O0)d56^BAIv~Y!5~5H?BBhqkxPQtEKQsh^vmm_<@39i;o1C(CVNkptZ?U= z*rd{PTwK5LxWsPLwAsub?#+MuDfXEEWXmO*+Qr{ythYS*x^$+XzR#B*EW6$wo4~lI heE*|;FvA&sq_P_tm`gs)`4R~d^K|udS?83{1OWA~czgf= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json b/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json index 35ec37005f..150667c818 100644 --- a/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json @@ -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 diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/terminal.png b/Resources/Textures/Mobs/Customization/human_hair.rsi/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..297518d9840a715e3b89956c5ecdd1feaf0794d7 GIT binary patch literal 1239 zcmV;|1StE7P)pJiAh93RCwC$TTLr0KJKyCTF+T)?XM*WEb#pN90ora zPW6+G-`w1UxaWCycehev_V@RdTpqZWx;YaJL#1GOR7HYryd)uy2h?p|o|~Ji9UL5} z>+S9B=l~`sCoA)ShlhvQ=bfFMHQTnV{(uaFgt4(P2n={n0`e$f^Yt5zM$psKqwafq zdz*egK0Xenr>Cvt*DVQ9XBa;?I9TH|8qeQd;4`+>`QqZD$@nC1D4hTqy(t*dv$LKjcL6TuB-zo%7PEML!ojHKm z+uL&%0-po~i2u>iQFRRxbT43-I3wWx{yx_5DgfgU|I7h#p`ia(DmFJa!>_L&8)IMn zarB-``REOBk9fblyi^DEawR`88D%{$QSQI_`FTYE3?~UX0k&uqNk%5mOUwZuyncUw zR~i56>Z<9TzP`RFOikfbX%}Fq0oT{pfgBi(jEq>@Q}@Miy1xhsbsirdFZ2<;ApnpA zE&yQw=tJU`60p9$?j#v*ZEbyPcVJ+ka0q+l0f=|HKL$e*dE(y*0ZU6uP7YOz|GWLMGySrdx zV?&+s6<%Lon|^1!xh#?pyV6ZWLl@`--OkR=biML)qoX5r?&|9LCJ-)3 z_msJzY_M&}l3_qys9T&D6ed6lK)DQ*flKlWR9Q4ZS&$7KpbJZQsH5%$Rs_Vw3>rpx z3TtqZGQP{6K(;McgZLb}K}V7xy&x$M)YBLykXmaqPAZa0^3c=}C|ZN} zQ;=qdQ4Z+u?^o{c3m69an81jDp`oGZnlV&l|Gm}7>J7dTpm$>!{rzUtvyuffs;df6 z7tnhHD+0Q^yQAyb*;!@u>FMc@|8J2b&<0}yc6N4RUpzlQ_pYKWR|o)cGrHB)Rj&Ud(75u)MtNUHqGFnPn2dJviS0ouPYS0{$%YZm;TM zh$7H+&DDkb+V7WY%Woi|unj)%ySHlw+Grya{{j6aBpw|NUJn2O002ovPDHLkV1iqN BIBfs` literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/terminal_headband.png b/Resources/Textures/Mobs/Customization/human_hair.rsi/terminal_headband.png new file mode 100644 index 0000000000000000000000000000000000000000..250725d0729f61c9e6a76737636ab9d048985657 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anMpkRcj zi(^Q|t+%%n`3@LxFdX=%{`ddt3YLArqQ<93vo*QmgvvxRN{wf$mlM`AuoES|ry zZ@N*j-BX}m5GYvCvEwgy&WrHpGW)B4O!9_^HvG~HXOJ&hI>U80NWjz8&t;ucLK6TG CS2qU$ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/terminal_highlight.png b/Resources/Textures/Mobs/Customization/human_hair.rsi/terminal_highlight.png new file mode 100644 index 0000000000000000000000000000000000000000..591f188c131eb327d67e124710f441fc5ab0048a GIT binary patch literal 848 zcmV-W1F!svP)pI0!c(cRCwC$T2G7GKon0FOUn`|Vx_if#kx>vDWpXT;-R_svVu^0 z$+d@G+E3!8pTG~GUqO5E(nD_RL6Lw5!3`cF1&e6knKY{%K1$^}K{#gV#_zYGAd+Ft6?m_@&TIuzA?QAytyxZ+QgK15ytF#Eruxdyw!B#30lvMfRlKowj>UI8Hed(ixHD1ps> zGMV(;XW#@h8jV#*vv2qN0PH)Ml1`^Zzx{HhfLM%0YzE~I&fj{L&*#OXAV5`BF%0KH z{&zlu1n8Yqo)ln(0&A@#$U;MsB+sOCw+zDCe=9HeaXu6R!b*ZRs@3X^!+xFTP63O> zg0=9`6Ih?zB*9Xt^iRK#2UZ6q06tAamHRsEzraC)t5pF1_~5K#8x;Az6kwX>K}ZO) zKW_H}a5cbWsA<}YS04fah60ihL_#DG2m}IwKp+qZe}Q$lsQb8UKj0Pa!WxFr?sPir zhN`NkL4dC7Sb@QMoBjRWj>qGtp#b=_d_14epTOl;FgEq*e4$WiAG>4pGg?G0Wuo6RWfwJCS0;A|IzAf=_1Ttr^H#j)C9;6Tj{0SvH&;B&Fx z?=KxEG`IrzAg)j9-UK*)vkamVdyLpffPDzT0qzb`?BUaP1voQ5Pw~HL%-dG6y-H&| zmY?<=^G*Wn8XRxHF*{F__)@@0Vtv{B--jXmV12BfmT+Fz{Xl(*0tvo-;JNNjX9fs_ ao%jPT^CKJ_J`>OY0000