]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Marking default coloring (#13039)
authorcsqrb <56765288+CaptainSqrBeard@users.noreply.github.com>
Sun, 5 Mar 2023 02:59:07 +0000 (08:59 +0600)
committerGitHub <noreply@github.com>
Sun, 5 Mar 2023 02:59:07 +0000 (18:59 -0800)
* Marking coloring WIP

* EnsureDefault now supports coloring!

* Now markings have coloring when they get added

* Many things

* yml files

* cleanup

* Some requested changes

* Nullable type and WIP caching

* Time to resolve that thing with deprecated hair fields

* Latest reviews + im still trying to use these hair markings

* FirstOrDefault thing and Tattoo docs

* IDK

* It's now works a bit more properly in preferences GUI

* THEY SYNCING! However preferences GUI still broken and doesn't work properly

* Markings now updating when changing in GUI. However they still don't work properly with bald humanoids

* Forgor...

* Default hair-colored markings will not color to hair if there is no hair

* Fixed default colors for customizable markings

* Fixed bug in prefs GUI that set current hair to null

* Now markings that must match skin color because of limb (e.x. Slimes) - will match skin color

* final tweaks: if hair uses skin color then markings will use skin color as hair color (slimes)

* fix

* fixed dirty. no more funni invis bug

* Mirrors and client profile loading

* default colors soon TM

* review + better coloring

* Hardcode is gone

* diona markings

* oh my god

* fixed CategoryColoring

* cool fallback, clean up and some other tweaks

* code style

* more style

* a

26 files changed:
Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml.cs
Content.Client/Humanoid/MarkingPicker.xaml.cs
Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs
Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs
Content.Server/MagicMirror/MagicMirrorSystem.cs
Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
Content.Shared/Humanoid/HumanoidCharacterAppearance.cs
Content.Shared/Humanoid/HumanoidVisualLayers.cs
Content.Shared/Humanoid/Markings/ColoringTypes/CategoryColoring.cs [new file with mode: 0644]
Content.Shared/Humanoid/Markings/ColoringTypes/EyeColoring.cs [new file with mode: 0644]
Content.Shared/Humanoid/Markings/ColoringTypes/SimpleColoring.cs [new file with mode: 0644]
Content.Shared/Humanoid/Markings/ColoringTypes/SkinColoring.cs [new file with mode: 0644]
Content.Shared/Humanoid/Markings/ColoringTypes/TattooColoring.cs [new file with mode: 0644]
Content.Shared/Humanoid/Markings/Marking.cs
Content.Shared/Humanoid/Markings/MarkingColoring.cs [new file with mode: 0644]
Content.Shared/Humanoid/Markings/MarkingManager.cs
Content.Shared/Humanoid/Markings/MarkingPrototype.cs
Content.Shared/Humanoid/Markings/MarkingsSet.cs
Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
Content.Shared/Humanoid/SharedHumanoidMarkingModifierSystem.cs
Resources/Prototypes/Entities/Mobs/Customization/Markings/cat_parts.yml
Resources/Prototypes/Entities/Mobs/Customization/Markings/diona.yml
Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml
Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml

index 9040ac7d1f6bc2c89c8ca998fe78e899dadc1656..47ef6a06074e851f37bf5c6f34334b14e780170a 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Shared.Ghost;
 using Content.Shared.Humanoid;
 using Content.Shared.Humanoid.Markings;
@@ -42,6 +43,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
         component.PermanentlyHidden = new(state.PermanentlyHidden);
 
         component.CustomBaseLayers = state.CustomBaseLayers.ShallowClone();
+
         UpdateLayers(component, sprite);
 
         ApplyMarkingSet(uid, state.Markings, component, sprite);
@@ -134,22 +136,66 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
         var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>();
 
         var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
-        var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
-            _prototypeManager);
-        markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
-
-        // legacy: remove in the future?
-        markings.RemoveCategory(MarkingCategories.Hair);
-        markings.RemoveCategory(MarkingCategories.FacialHair);
+        var markings = new MarkingSet(speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
 
-        var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
-        markings.AddBack(MarkingCategories.Hair, hair);
+        // Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it.
+        var markingFColored = new Dictionary<Marking, MarkingPrototype>();
+        foreach (var marking in profile.Appearance.Markings)
+        {
+            if (_markingManager.TryGetMarking(marking, out var prototype))
+            {
+                if (!prototype.ForcedColoring)
+                {
+                    markings.AddBack(prototype.MarkingCategory, marking);
+                }
+                else
+                {
+                    markingFColored.Add(marking, prototype);
+                }
+            }
+        }
 
+        // legacy: remove in the future?
+        //markings.RemoveCategory(MarkingCategories.Hair);
+        //markings.RemoveCategory(MarkingCategories.FacialHair);
+
+        // We need to ensure hair before applying it or coloring can try depend on markings that can be invalid
+        var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, _prototypeManager)
+            ? profile.Appearance.SkinColor : profile.Appearance.HairColor;
+        var hair = new Marking(profile.Appearance.HairStyleId,
+            new[] { hairColor });
+    
+        var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, _prototypeManager)
+            ? profile.Appearance.SkinColor : profile.Appearance.FacialHairColor;
         var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
-            new[] { profile.Appearance.FacialHairColor });
-        markings.AddBack(MarkingCategories.FacialHair, facialHair);
+            new[] { facialHairColor });
 
-        markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
+        if (_markingManager.CanBeApplied(profile.Species, hair, _prototypeManager))
+        {
+            markings.AddBack(MarkingCategories.Hair, hair);
+        }
+        if (_markingManager.CanBeApplied(profile.Species, facialHair, _prototypeManager))
+        {
+            markings.AddBack(MarkingCategories.FacialHair, facialHair);
+        }
+        
+        // Finally adding marking with forced colors
+        foreach (var (marking, prototype) in markingFColored)
+        {
+            var markingColors = MarkingColoring.GetMarkingLayerColors(
+                prototype,
+                profile.Appearance.SkinColor,
+                profile.Appearance.EyeColor,
+                markings
+            );
+            markings.AddBack(prototype.MarkingCategory, new Marking(marking.MarkingId, markingColors));
+        }
+        
+        markings.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _prototypeManager);
+        markings.EnsureDefault(
+            profile.Appearance.SkinColor, 
+            profile.Appearance.EyeColor,
+            _markingManager);
 
         DebugTools.Assert(uid.IsClientSide());
 
@@ -239,7 +285,6 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
             spriteComp.RemoveLayer(index);
         }
     }
-
     private void ApplyMarking(EntityUid uid,
         MarkingPrototype markingPrototype,
         IReadOnlyList<Color>? colors,
@@ -273,22 +318,19 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
             }
 
             sprite.LayerSetVisible(layerId, visible);
-
+            
             if (!visible || setting == null) // this is kinda implied
             {
                 continue;
             }
 
-            if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
+            if (colors != null) 
             {
-                var skinColor = humanoid.SkinColor;
-                skinColor.A = setting.LayerAlpha;
-
-                sprite.LayerSetColor(layerId, skinColor);
+                sprite.LayerSetColor(layerId, colors[j]);
             }
             else
             {
-                sprite.LayerSetColor(layerId, colors[j]);
+                sprite.LayerSetColor(layerId, Color.White);
             }
         }
     }
index 178e9a173e894873050db78b5afef9398555d95f..ea39c501b3dda468f7868d8fd3085364d7c657d4 100644 (file)
@@ -60,11 +60,13 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow
         string? state = _protoMan.HasIndex<HumanoidSpeciesSpriteLayer>(modifier.Text) ? modifier.Text : null;
         OnLayerInfoModified?.Invoke(layer, new CustomBaseLayerInfo(state, modifier.Color));
     }
-
-    public void SetState(MarkingSet markings, string species, Color skinColor, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> info)
+    public void SetState(
+        MarkingSet markings,
+        string species,
+        Color skinColor,
+        Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> info
+    )
     {
-        MarkingPickerWidget.SetData(markings, species, skinColor);
-
         foreach (var (layer, modifier) in _modifiers)
         {
             if (!info.TryGetValue(layer, out var layerInfo))
@@ -75,6 +77,14 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow
 
             modifier.SetState(true, layerInfo.ID ?? string.Empty, layerInfo.Color ?? Color.White);
         }
+
+        var eyesColor = Color.White;
+        if (info.TryGetValue(HumanoidVisualLayers.Eyes, out var eyes) && eyes.Color != null)
+        {
+            eyesColor = eyes.Color.Value;
+        }
+
+        MarkingPickerWidget.SetData(markings, species, skinColor, eyesColor);
     }
 
     private sealed class HumanoidBaseLayerModifier : BoxContainer
index 6d9e94f7f641c6080f9d661a4319a825afd20db3..f284b84cd2e8c739413828f902cd79babb00683c 100644 (file)
@@ -36,6 +36,9 @@ public sealed partial class MarkingPicker : Control
 
     private string _currentSpecies = SharedHumanoidAppearanceSystem.DefaultSpecies;
     public Color CurrentSkinColor = Color.White;
+    public Color CurrentEyeColor = Color.Black;
+    public Marking? HairMarking;
+    public Marking? FacialHairMarking;
 
     private readonly HashSet<MarkingCategories> _ignoreCategories = new();
 
@@ -74,7 +77,7 @@ public sealed partial class MarkingPicker : Control
         }
     }
 
-    public void SetData(List<Marking> newMarkings, string species, Color skinColor)
+    public void SetData(List<Marking> newMarkings, string species, Color skinColor, Color eyeColor)
     {
         var pointsProto = _prototypeManager
             .Index<SpeciesPrototype>(species).MarkingPoints;
@@ -82,33 +85,36 @@ public sealed partial class MarkingPicker : Control
 
         if (!IgnoreSpecies)
         {
-            _currentMarkings.FilterSpecies(species); // should be validated server-side but it can't hurt
+            _currentMarkings.EnsureSpecies(species, skinColor, _markingManager); // should be validated server-side but it can't hurt
         }
 
         _currentSpecies = species;
         CurrentSkinColor = skinColor;
+        CurrentEyeColor = eyeColor;
 
         Populate();
         PopulateUsed();
     }
 
-    public void SetData(MarkingSet set, string species, Color skinColor)
+    public void SetData(MarkingSet set, string species, Color skinColor, Color eyeColor)
     {
         _currentMarkings = set;
 
         if (!IgnoreSpecies)
         {
-            _currentMarkings.FilterSpecies(species); // should be validated server-side but it can't hurt
+            _currentMarkings.EnsureSpecies(species, skinColor, _markingManager); // should be validated server-side but it can't hurt
         }
 
         _currentSpecies = species;
         CurrentSkinColor = skinColor;
+        CurrentEyeColor = eyeColor;
 
         Populate();
         PopulateUsed();
     }
 
     public void SetSkinColor(Color color) => CurrentSkinColor = color;
+    public void SetEyeColor(Color color) => CurrentEyeColor = color;
 
     public MarkingPicker()
     {
@@ -201,7 +207,7 @@ public sealed partial class MarkingPicker : Control
 
         if (!IgnoreSpecies)
         {
-            _currentMarkings.FilterSpecies(_currentSpecies, _markingManager);
+            _currentMarkings.EnsureSpecies(_currentSpecies, null, _markingManager); 
         }
 
         // walk backwards through the list for visual purposes
@@ -305,7 +311,7 @@ public sealed partial class MarkingPicker : Control
         var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(species);
 
         _currentMarkings = new(markingList, speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
-        _currentMarkings.FilterSpecies(species);
+        _currentMarkings.EnsureSpecies(species, null, _markingManager);
 
         Populate();
         PopulateUsed();
@@ -335,7 +341,7 @@ public sealed partial class MarkingPicker : Control
         _selectedMarking = CMarkingsUsed[item.ItemIndex];
         var prototype = (MarkingPrototype) _selectedMarking.Metadata!;
 
-        if (prototype.FollowSkinColor)
+        if (prototype.ForcedColoring)
         {
             CMarkingColors.Visible = false;
 
@@ -412,12 +418,40 @@ public sealed partial class MarkingPicker : Control
         }
 
         var marking = (MarkingPrototype) _selectedUnusedMarking.Metadata!;
+        var markingObject = marking.AsMarking();
 
+        // We need add hair markings in cloned set manually because _currentMarkings doesn't have it
+        var markingSet = new MarkingSet(_currentMarkings);
+        if (HairMarking != null)
+        {
+            markingSet.AddBack(MarkingCategories.Hair, HairMarking);
+        }
+        if (FacialHairMarking != null) 
+        {
+            markingSet.AddBack(MarkingCategories.FacialHair, FacialHairMarking);
+        }
 
-        var markingObject = marking.AsMarking();
-        for (var i = 0; i < markingObject.MarkingColors.Count; i++)
+        if (!_markingManager.MustMatchSkin(_currentSpecies, marking.BodyPart, _prototypeManager))
         {
-            markingObject.SetColor(i, CurrentSkinColor);
+            // Do default coloring
+            var colors = MarkingColoring.GetMarkingLayerColors(
+                marking,
+                CurrentSkinColor,
+                CurrentEyeColor,
+                markingSet
+            );
+            for (var i = 0; i < colors.Count; i++)
+            {
+                markingObject.SetColor(i, colors[i]);
+            }
+        }
+        else
+        {
+            // Color everything in skin color
+            for (var i = 0; i < marking.Sprites.Count; i++)
+            {
+                markingObject.SetColor(i, CurrentSkinColor);
+            }
         }
 
         markingObject.Forced = Forced;
index 397c194b0a809f52b03ca28073eb435ddfbffea7..4330a8794562d816216e0295c727408e729ba94c 100644 (file)
@@ -53,7 +53,7 @@ namespace Content.Client.Preferences.UI
         private readonly IEntityManager _entMan;
         private readonly IConfigurationManager _configurationManager;
         private readonly MarkingManager _markingManager;
-
+        
         private LineEdit _ageEdit => CAgeEdit;
         private LineEdit _nameEdit => CNameEdit;
         private LineEdit _flavorTextEdit = null!;
@@ -223,6 +223,7 @@ namespace Content.Client.Preferences.UI
                     return;
                 Profile = Profile.WithCharacterAppearance(
                     Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
+                UpdateCMarkingsHair();
                 IsDirty = true;
             };
 
@@ -241,6 +242,7 @@ namespace Content.Client.Preferences.UI
                     return;
                 Profile = Profile.WithCharacterAppearance(
                     Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
+                UpdateCMarkingsFacialHair();
                 IsDirty = true;
             };
 
@@ -252,6 +254,7 @@ namespace Content.Client.Preferences.UI
                     Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
                 );
                 UpdateHairPickers();
+                UpdateCMarkingsHair();
                 IsDirty = true;
             };
 
@@ -263,6 +266,7 @@ namespace Content.Client.Preferences.UI
                     Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
                 );
                 UpdateHairPickers();
+                UpdateCMarkingsFacialHair();
                 IsDirty = true;
             };
 
@@ -282,7 +286,7 @@ namespace Content.Client.Preferences.UI
                 );
 
                 UpdateHairPickers();
-
+                UpdateCMarkingsHair();
                 IsDirty = true;
             };
 
@@ -302,7 +306,7 @@ namespace Content.Client.Preferences.UI
                 );
 
                 UpdateHairPickers();
-
+                UpdateCMarkingsFacialHair();
                 IsDirty = true;
             };
 
@@ -343,6 +347,7 @@ namespace Content.Client.Preferences.UI
                     return;
                 Profile = Profile.WithCharacterAppearance(
                     Profile.Appearance.WithEyeColor(newColor));
+                CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
                 IsDirty = true;
             };
 
@@ -646,7 +651,7 @@ namespace Content.Client.Preferences.UI
                     var color = SkinColor.HumanSkinTone((int) _skinColor.Value);
 
                     CMarkings.CurrentSkinColor = color;
-                    Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
+                    Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
                     break;
                 }
                 case HumanoidSkinColor.Hues:
@@ -746,8 +751,8 @@ namespace Content.Client.Preferences.UI
             Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
             CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
 
-            _needUpdatePreview = true;
             UpdateControls();
+            _needUpdatePreview = true;
         }
 
         private void SetAge(int newAge)
@@ -941,7 +946,9 @@ namespace Content.Client.Preferences.UI
                 return;
             }
 
-            CMarkings.SetData(Profile.Appearance.Markings, Profile.Species, Profile.Appearance.SkinColor);
+            CMarkings.SetData(Profile.Appearance.Markings, Profile.Species,
+                Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
+            );
         }
 
         private void UpdateSpecies()
@@ -990,7 +997,6 @@ namespace Content.Client.Preferences.UI
             {
                 return;
             }
-
             var hairMarking = Profile.Appearance.HairStyleId switch
             {
                 HairStyles.DefaultHairStyle => new List<Marking>(),
@@ -1013,6 +1019,76 @@ namespace Content.Client.Preferences.UI
                 1);
         }
 
+        private void UpdateCMarkingsHair()
+        {
+            if (Profile == null)
+            {
+                return;
+            }
+
+            // hair color
+            Color? hairColor = null;
+            if ( Profile.Appearance.HairStyleId != HairStyles.DefaultHairStyle &&
+                _markingManager.Markings.TryGetValue(Profile.Appearance.HairStyleId, out var hairProto)
+            )
+            {
+                if (_markingManager.CanBeApplied(Profile.Species, hairProto, _prototypeManager))
+                {
+                    if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, _prototypeManager))
+                    {
+                        hairColor = Profile.Appearance.SkinColor;
+                    }
+                    else
+                    {
+                        hairColor = Profile.Appearance.HairColor;
+                    }
+                }
+            }
+            if (hairColor != null)
+            {
+                CMarkings.HairMarking = new (Profile.Appearance.HairStyleId, new List<Color>() { hairColor.Value });
+            }
+            else
+            {
+                CMarkings.HairMarking = null;
+            }
+        }
+
+        private void UpdateCMarkingsFacialHair()
+        {
+            if (Profile == null)
+            {
+                return;
+            }
+            
+            // facial hair color
+            Color? facialHairColor = null;
+            if ( Profile.Appearance.FacialHairStyleId != HairStyles.DefaultFacialHairStyle &&
+                _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto)
+            )
+            {
+                if (_markingManager.CanBeApplied(Profile.Species, facialHairProto, _prototypeManager))
+                {
+                    if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, _prototypeManager))
+                    {
+                        facialHairColor = Profile.Appearance.SkinColor;
+                    }
+                    else
+                    {
+                        facialHairColor = Profile.Appearance.FacialHairColor;
+                    }
+                }
+            }
+            if (facialHairColor != null)
+            {
+                CMarkings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List<Color>() { facialHairColor.Value });
+            }
+            else
+            {
+                CMarkings.FacialHairMarking = null;
+            }
+        }
+
         private void UpdateEyePickers()
         {
             if (Profile == null)
@@ -1020,6 +1096,7 @@ namespace Content.Client.Preferences.UI
                 return;
             }
 
+            CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
             _eyesPicker.SetData(Profile.Appearance.EyeColor);
         }
 
@@ -1049,7 +1126,6 @@ namespace Content.Client.Preferences.UI
             UpdateClothingControls();
             UpdateBackpackControls();
             UpdateAgeEdit();
-            UpdateHairPickers();
             UpdateEyePickers();
             UpdateSaveButton();
             UpdateJobPriorities();
@@ -1057,6 +1133,9 @@ namespace Content.Client.Preferences.UI
             UpdateTraitPreferences();
             UpdateMarkings();
             RebuildSpriteView();
+            UpdateHairPickers();
+            UpdateCMarkingsHair();
+            UpdateCMarkingsFacialHair();
 
             _preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
         }
index ed83b7c1899e4df556180c4f898c0c2d3d87fee2..669c1cb1b436a1e8d99759faac5ec8d46ab368fa 100644 (file)
@@ -47,6 +47,7 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS
         }
 
         LoadProfile(uid, startingSet.Profile, humanoid);
+        
     }
 
     private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, ExaminedEvent args)
@@ -79,16 +80,56 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS
 
         humanoid.MarkingSet.Clear();
 
+        // Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it.
+        var markingFColored = new Dictionary<Marking, MarkingPrototype>();
+        foreach (var marking in profile.Appearance.Markings)
+        {
+            if (_markingManager.TryGetMarking(marking, out var prototype))
+            {
+                if (!prototype.ForcedColoring)
+                {
+                    AddMarking(uid, marking.MarkingId, marking.MarkingColors, false);
+                }
+                else
+                {
+                    markingFColored.Add(marking, prototype);
+                }
+            }
+        }
+
         // Hair/facial hair - this may eventually be deprecated.
+        // We need to ensure hair before applying it or coloring can try depend on markings that can be invalid
+        var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, _prototypeManager)
+            ? profile.Appearance.SkinColor : profile.Appearance.HairColor;
+        var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, _prototypeManager)
+            ? profile.Appearance.SkinColor : profile.Appearance.FacialHairColor;
+        
+        if (_markingManager.Markings.TryGetValue(profile.Appearance.HairStyleId, out var hairPrototype) &&
+            _markingManager.CanBeApplied(profile.Species, hairPrototype, _prototypeManager))
+        {
+            AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false);
+        }
+        
+        if (_markingManager.Markings.TryGetValue(profile.Appearance.FacialHairStyleId, out var facialHairPrototype) &&
+            _markingManager.CanBeApplied(profile.Species, facialHairPrototype, _prototypeManager))
+        {
+            AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false); 
+        }
 
-        AddMarking(uid, profile.Appearance.HairStyleId, profile.Appearance.HairColor, false);
-        AddMarking(uid, profile.Appearance.FacialHairStyleId, profile.Appearance.FacialHairColor, false);
+        humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _prototypeManager);
 
-        foreach (var marking in profile.Appearance.Markings)
+        // Finally adding marking with forced colors
+        foreach (var (marking, prototype) in markingFColored)
         {
-            AddMarking(uid, marking.MarkingId, marking.MarkingColors, false);
+            var markingColors = MarkingColoring.GetMarkingLayerColors(
+                prototype,
+                profile.Appearance.SkinColor,
+                profile.Appearance.EyeColor,
+                humanoid.MarkingSet
+            );
+            AddMarking(uid, marking.MarkingId, markingColors, false);
         }
-
+        
         EnsureDefaultMarkings(uid, humanoid);
 
         humanoid.Gender = profile.Gender;
@@ -336,7 +377,6 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS
         {
             return;
         }
-
-        humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, _markingManager);
+        humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, humanoid.EyeColor, _markingManager);
     }
 }
index bcd65d77bec89559aa22caec1c356869761071cf..cd5cbda3187eab487663be185b7feddfeccc22f3 100644 (file)
@@ -36,7 +36,10 @@ public sealed partial class HumanoidAppearanceSystem
                 _uiSystem.TrySetUiState(
                     uid,
                     HumanoidMarkingModifierKey.Key,
-                    new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
+                    new HumanoidMarkingModifierState(component.MarkingSet, component.Species,
+                        component.SkinColor,
+                        component.CustomBaseLayers
+                    ));
             }
         });
     }
@@ -66,7 +69,10 @@ public sealed partial class HumanoidAppearanceSystem
             _uiSystem.TrySetUiState(
                 uid,
                 HumanoidMarkingModifierKey.Key,
-                new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
+                new HumanoidMarkingModifierState(component.MarkingSet, component.Species,
+                        component.SkinColor,
+                        component.CustomBaseLayers
+                    ));
         }
     }
 
@@ -87,7 +93,10 @@ public sealed partial class HumanoidAppearanceSystem
             _uiSystem.TrySetUiState(
                 uid,
                 HumanoidMarkingModifierKey.Key,
-                new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
+                new HumanoidMarkingModifierState(component.MarkingSet, component.Species,
+                        component.SkinColor,
+                        component.CustomBaseLayers
+                    ));
         }
 
     }
index c242b0b936883de6a8a0e85b23292816cc27b32d..90a0b19b7ddd713216ac2bfc5d7fdb9cb99b3e84 100644 (file)
@@ -177,4 +177,4 @@ public sealed class MagicMirrorSystem : EntitySystem
 
         UpdateInterface(uid, args.User, args.Session);
     }
-}
+}
\ No newline at end of file
index 35c713d5e1e15bccd093dffbb20291d7e859e17e..35444842cbfd6933e3c0e72ddfae318fd1a4b2dd 100644 (file)
@@ -70,6 +70,18 @@ public sealed class HumanoidAppearanceComponent : Component
 
     [DataField("eyeColor")]
     public Color EyeColor = Color.Brown;
+
+    /// <summary>
+    ///     Hair color of this humanoid. Used to avoid looping through all markings
+    /// </summary>
+    [ViewVariables(VVAccess.ReadOnly)]
+    public Color? CachedHairColor;
+
+    /// <summary>
+    ///     Facial Hair color of this humanoid. Used to avoid looping through all markings
+    /// </summary>
+    [ViewVariables(VVAccess.ReadOnly)]
+    public Color? CachedFacialHairColor;
 }
 
 [Serializable, NetSerializable]
index 6c03ec746ccb06a55cb0f5d09c4cb1fc86956964..5d000fd2706cc439a2365b68553fc50721b22854 100644 (file)
@@ -217,7 +217,6 @@ namespace Content.Shared.Humanoid
             {
                 markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto);
                 markingSet.EnsureValid(markingManager);
-                markingSet.FilterSpecies(species, markingManager);
 
                 switch (speciesProto.SkinColoration)
                 {
@@ -236,6 +235,7 @@ namespace Content.Shared.Humanoid
 
                         break;
                 }
+                markingSet.EnsureSpecies(species, skinColor, markingManager);
             }
 
             return new HumanoidCharacterAppearance(
index 5d587f982c151abc24714b3b25962042f5d5088a..938b789f7aefd62fabcc5fa817f5fa8b59ce36ed 100644 (file)
@@ -1,4 +1,5 @@
-using Robust.Shared.Serialization;
+using Content.Shared.Humanoid.Markings;
+using Robust.Shared.Serialization;
 
 namespace Content.Shared.Humanoid
 {
diff --git a/Content.Shared/Humanoid/Markings/ColoringTypes/CategoryColoring.cs b/Content.Shared/Humanoid/Markings/ColoringTypes/CategoryColoring.cs
new file mode 100644 (file)
index 0000000..1ed3e53
--- /dev/null
@@ -0,0 +1,24 @@
+using System.Linq;
+
+namespace Content.Shared.Humanoid.Markings;
+
+/// <summary>
+///     Colors marking in color of first defined marking from specified category (in e.x. from Hair category)
+/// </summary>
+public sealed class CategoryColoring : LayerColoringType
+{
+    [DataField("category", required: true)]
+    public MarkingCategories Category;
+
+    public override Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        Color? outColor = null;
+        if (markingSet.TryGetCategory(Category, out var markings) &&
+            markings.Count > 0)
+        {
+            outColor = markings[0].MarkingColors.FirstOrDefault();
+        }
+
+        return outColor;
+    }
+}
diff --git a/Content.Shared/Humanoid/Markings/ColoringTypes/EyeColoring.cs b/Content.Shared/Humanoid/Markings/ColoringTypes/EyeColoring.cs
new file mode 100644 (file)
index 0000000..0c78654
--- /dev/null
@@ -0,0 +1,12 @@
+namespace Content.Shared.Humanoid.Markings;
+
+/// <summary>
+///     Colors layer in an eye color
+/// </summary>
+public sealed class EyeColoring : LayerColoringType
+{
+    public override Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        return eyes;
+    }
+}
diff --git a/Content.Shared/Humanoid/Markings/ColoringTypes/SimpleColoring.cs b/Content.Shared/Humanoid/Markings/ColoringTypes/SimpleColoring.cs
new file mode 100644 (file)
index 0000000..2ee68b8
--- /dev/null
@@ -0,0 +1,15 @@
+namespace Content.Shared.Humanoid.Markings;
+
+/// <summary>
+///     Colors layer in a specified color
+/// </summary>
+public sealed class SimpleColoring : LayerColoringType
+{
+    [DataField("color", required: true)]
+    public Color Color = Color.White;
+
+    public override Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        return Color;
+    }
+}
diff --git a/Content.Shared/Humanoid/Markings/ColoringTypes/SkinColoring.cs b/Content.Shared/Humanoid/Markings/ColoringTypes/SkinColoring.cs
new file mode 100644 (file)
index 0000000..49cdb79
--- /dev/null
@@ -0,0 +1,12 @@
+namespace Content.Shared.Humanoid.Markings;
+
+/// <summary>
+///     Colors layer in a skin color
+/// </summary>
+public sealed class SkinColoring : LayerColoringType
+{
+    public override Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        return skin;
+    }
+}
diff --git a/Content.Shared/Humanoid/Markings/ColoringTypes/TattooColoring.cs b/Content.Shared/Humanoid/Markings/ColoringTypes/TattooColoring.cs
new file mode 100644 (file)
index 0000000..225ad67
--- /dev/null
@@ -0,0 +1,20 @@
+namespace Content.Shared.Humanoid.Markings;
+
+/// <summary>
+///     Colors layer in skin color but much darker.
+/// </summary>
+public sealed class TattooColoring : LayerColoringType
+{
+    public override Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        if (skin == null)
+        {
+            return null;
+        }
+        
+        var newColor = Color.ToHsv(skin.Value);
+        newColor.Z = .40f;
+
+        return Color.FromHsv(newColor);
+    }
+}
index 25413da91e26de703c80988dc4b389877c8f49d8..c7d5cb6184dd8dafca2c604bc2cf6983387ac0ad 100644 (file)
@@ -1,4 +1,6 @@
 using System.Linq;
+using Content.Shared.Humanoid.Prototypes;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
 
 namespace Content.Shared.Humanoid.Markings
@@ -67,6 +69,14 @@ namespace Content.Shared.Humanoid.Markings
         public void SetColor(int colorIndex, Color color) =>
             _markingColors[colorIndex] = color;
 
+        public void SetColor(Color color)
+        {
+            for (int i = 0; i < _markingColors.Count; i++)
+            {
+                _markingColors[i] = color;
+            }
+        }
+
         public int CompareTo(Marking? marking)
         {
             if (marking == null)
diff --git a/Content.Shared/Humanoid/Markings/MarkingColoring.cs b/Content.Shared/Humanoid/Markings/MarkingColoring.cs
new file mode 100644 (file)
index 0000000..d90fdec
--- /dev/null
@@ -0,0 +1,147 @@
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Humanoid.Markings;
+
+/// <summary>
+///     Default colors for marking 
+/// </summary>
+[DataDefinition]
+public sealed class MarkingColors
+{
+    /// <summary>
+    /// Coloring properties that will be used on any unspecified layer
+    /// </summary>
+    [DataField("default", true)]
+    public LayerColoringDefinition Default = new LayerColoringDefinition();
+
+    /// <summary>
+    ///     Layers with their own coloring type and properties
+    /// </summary>
+    [DataField("layers", true)]
+    public Dictionary<string, LayerColoringDefinition>? Layers;
+}
+
+public static class MarkingColoring
+{
+    /// <summary>
+    ///     Returns list of colors for marking layers
+    /// </summary>
+    public static List<Color> GetMarkingLayerColors
+    (
+        MarkingPrototype prototype,
+        Color? skinColor,
+        Color? eyeColor,
+        MarkingSet markingSet
+    )
+    {
+        var colors = new List<Color>();
+
+        // Coloring from default properties
+        var defaultColor = prototype.Coloring.Default.GetColor(skinColor, eyeColor, markingSet);
+
+        if (prototype.Coloring.Layers == null)
+        {
+            // If layers is not specified, then every layer must be default
+            for (var i = 0; i < prototype.Sprites.Count; i++)
+            {
+                colors.Add(defaultColor);
+            }
+            return colors;
+        }
+        else
+        {
+            // If some layers are specified.
+            for (var i = 0; i < prototype.Sprites.Count; i++)
+            {
+                // Getting layer name
+                string? name = prototype.Sprites[i] switch
+                {
+                    SpriteSpecifier.Rsi rsi => rsi.RsiState,
+                    SpriteSpecifier.Texture texture => texture.TexturePath.Filename,
+                    _ => null
+                };
+                if (name == null)
+                {
+                    colors.Add(defaultColor);
+                    continue;
+                }
+            
+                // All specified layers must be colored separately, all unspecified must depend on default coloring
+                if (prototype.Coloring.Layers.TryGetValue(name, out var layerColoring))
+                {
+                    var marking_color = layerColoring.GetColor(skinColor, eyeColor, markingSet);
+                    colors.Add(marking_color);
+                }
+                else
+                {
+                    colors.Add(defaultColor);
+                }
+            }
+            return colors;
+        }
+    }
+}
+
+/// <summary>
+///     A class that defines coloring type and fallback for markings
+/// </summary>
+[DataDefinition]
+public sealed class LayerColoringDefinition
+{
+    [DataField("type")]
+    public LayerColoringType Type = new SkinColoring();
+
+    /// <summary>
+    ///     Coloring types that will be used if main coloring type will return nil
+    /// </summary>
+    [DataField("fallbackTypes")]
+    public List<LayerColoringType> FallbackTypes = new() {};
+    
+    /// <summary>
+    ///     Color that will be used if coloring type and fallback type will return nil
+    /// </summary>
+    [DataField("fallbackColor")]
+    public Color FallbackColor = Color.White;
+
+    public Color GetColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        var color = Type.GetColor(skin, eyes, markingSet);
+        if (color == null)
+        {
+            foreach (var type in FallbackTypes)
+            {
+                color = type.GetColor(skin, eyes, markingSet);
+                if (color != null) break;
+            }
+        }
+        return color ?? FallbackColor;
+    }
+}
+
+/// <summary>
+///     An abstract class for coloring types
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+public abstract class LayerColoringType
+{
+    /// <summary>
+    ///     Makes output color negative
+    /// </summary>
+    [DataField("negative")]
+    public bool Negative { get; } = false;
+    public abstract Color? GetCleanColor(Color? skin, Color? eyes, MarkingSet markingSet);
+    public Color? GetColor(Color? skin, Color? eyes, MarkingSet markingSet)
+    {
+        var color = GetCleanColor(skin, eyes, markingSet);
+        // Negative color
+        if (color != null && Negative)
+        {
+            var rcolor = color.Value;
+            rcolor.R = 1f-rcolor.R;
+            rcolor.G = 1f-rcolor.G;
+            rcolor.B = 1f-rcolor.B;
+            return rcolor;
+        }
+        return color;
+    }
+}
\ No newline at end of file
index b1f4477c3d5486d86f09b93d7e36cdea7c580a8e..08a721d3450b86358673529cfebd0a30647f1fc5 100644 (file)
@@ -120,5 +120,69 @@ namespace Content.Shared.Humanoid.Markings
                 _index.Add(markingPrototype);
             }
         }
+
+        public bool CanBeApplied(string species, Marking marking, IPrototypeManager? prototypeManager = null)
+        {
+            IoCManager.Resolve(ref prototypeManager);
+
+            var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
+            var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
+
+            if (!TryGetMarking(marking, out var prototype))
+            {
+                return false;
+            }
+
+            if (onlyWhitelisted && prototype.SpeciesRestrictions == null)
+            {
+                return false;
+            }
+
+            if (prototype.SpeciesRestrictions != null
+                && !prototype.SpeciesRestrictions.Contains(species))
+            {
+                return false;
+            }
+            return true;
+        }
+
+        public bool CanBeApplied(string species, MarkingPrototype prototype, IPrototypeManager? prototypeManager = null)
+        {
+            IoCManager.Resolve(ref prototypeManager);
+
+            var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
+            var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
+
+            if (onlyWhitelisted && prototype.SpeciesRestrictions == null)
+            {
+                return false;
+            }
+
+            if (prototype.SpeciesRestrictions != null &&
+                !prototype.SpeciesRestrictions.Contains(species))
+            {
+                return false;
+            }
+            return true;
+        }
+
+        public bool MustMatchSkin(string species, HumanoidVisualLayers layer, IPrototypeManager? prototypeManager = null)
+        {
+            IoCManager.Resolve(ref prototypeManager);
+
+            var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
+            if (
+                !prototypeManager.TryIndex(speciesProto.SpriteSet, out HumanoidSpeciesBaseSpritesPrototype? baseSprites) ||
+                !baseSprites.Sprites.TryGetValue(layer, out var spriteName) ||
+                !prototypeManager.TryIndex(spriteName, out HumanoidSpeciesSpriteLayer? sprite) ||
+                sprite == null ||
+                !sprite.MarkingsMatchSkin
+            )
+            {
+                return false;
+            }
+            
+            return true;
+        }
     }
 }
index bda78f6421f360573c5188e2700acd701a52290c..3ceb3197616f67398cd63ca4ab138247ad6a1bd3 100644 (file)
@@ -16,13 +16,19 @@ namespace Content.Shared.Humanoid.Markings
 
         [DataField("markingCategory", required: true)]
         public MarkingCategories MarkingCategory { get; } = default!;
-
+        
         [DataField("speciesRestriction")]
         public List<string>? SpeciesRestrictions { get; }
 
         [DataField("followSkinColor")]
         public bool FollowSkinColor { get; } = false;
 
+        [DataField("forcedColoring")]
+        public bool ForcedColoring { get; } = false;
+
+        [DataField("coloring")]
+        public MarkingColors Coloring { get; } = new();
+
         [DataField("sprites", required: true)]
         public List<SpriteSpecifier> Sprites { get; private set; } = default!;
 
index 4a6a5163ffdd1710dd06991d6de44259790de403..9535e28e1adaa4692083a24864d8f32526051488 100644 (file)
@@ -4,6 +4,7 @@ using System.Linq;
 using Content.Shared.Humanoid.Prototypes;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
 
 namespace Content.Shared.Humanoid.Markings;
 
@@ -104,6 +105,22 @@ public sealed class MarkingSet
         }
     }
 
+    /// <summary>
+    ///     Construct a MarkingSet only with a points dictionary.
+    /// </summary>
+    /// <param name="pointsPrototype">The ID of the points dictionary prototype.</param>
+    public MarkingSet(string pointsPrototype, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
+    {
+        IoCManager.Resolve(ref markingManager, ref prototypeManager);
+
+        if (!prototypeManager.TryIndex(pointsPrototype, out MarkingPointsPrototype? points))
+        {
+            return;
+        }
+
+        Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
+    }
+
     /// <summary>
     ///     Construct a MarkingSet by deep cloning another set.
     /// </summary>
@@ -122,12 +139,13 @@ public sealed class MarkingSet
     }
 
     /// <summary>
-    ///     Filters markings based on species restrictions in the marking's prototype from this marking set.
+    ///     Filters and colors markings based on species and it's restrictions in the marking's prototype from this marking set.
     /// </summary>
     /// <param name="species">The species to filter.</param>
+    /// <param name="skinColor">The skin color for recoloring (i.e. slimes). Use null if you want only filter markings</param>
     /// <param name="markingManager">Marking manager.</param>
     /// <param name="prototypeManager">Prototype manager.</param>
-    public void FilterSpecies(string species, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
+    public void EnsureSpecies(string species, Color? skinColor, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
     {
         IoCManager.Resolve(ref markingManager);
         IoCManager.Resolve(ref prototypeManager);
@@ -163,6 +181,22 @@ public sealed class MarkingSet
         {
             Remove(remove.category, remove.id);
         }
+
+        // Re-color left markings them into skin color if needed (i.e. for slimes)
+        if (skinColor != null)
+        {
+            foreach (var (category, list) in Markings)
+            {
+                foreach (var marking in list)
+                {
+                    if (markingManager.TryGetMarking(marking, out var prototype) &&
+                        markingManager.MustMatchSkin(species, prototype.BodyPart, prototypeManager))
+                    {
+                        marking.SetColor(skinColor.Value);
+                    }
+                }
+            }
+        }
     }
 
     /// <summary>
@@ -200,9 +234,11 @@ public sealed class MarkingSet
     /// <summary>
     ///     Ensures that the default markings as defined by the marking point set in this marking set are applied.
     /// </summary>
-    /// <param name="skinColor">Color to apply.</param>
+    /// <param name="skinColor">Skin color for marking coloring.</param>
+    /// <param name="eyeColor">Eye color for marking coloring.</param>
+    /// <param name="hairColor">Hair color for marking coloring.</param>
     /// <param name="markingManager">Marking manager.</param>
-    public void EnsureDefault(Color? skinColor = null, MarkingManager? markingManager = null)
+    public void EnsureDefault(Color? skinColor = null, Color? eyeColor = null, MarkingManager? markingManager = null)
     {
         IoCManager.Resolve(ref markingManager);
 
@@ -218,22 +254,13 @@ public sealed class MarkingSet
             {
                 if (markingManager.Markings.TryGetValue(points.DefaultMarkings[index], out var prototype))
                 {
-                    Marking marking;
-                    if (skinColor == null)
-                    {
-                        marking = new Marking(points.DefaultMarkings[index], prototype.Sprites.Count);
-                    }
-                    else
-                    {
-                        var colors = new List<Color>();
-
-                        for (var i = 0; i < prototype.Sprites.Count; i++)
-                        {
-                            colors.Add(skinColor.Value);
-                        }
-
-                        marking = new Marking(points.DefaultMarkings[index], colors);
-                    }
+                    var colors = MarkingColoring.GetMarkingLayerColors(
+                            prototype,
+                            skinColor,
+                            eyeColor,
+                            this
+                        );
+                    var marking = new Marking(points.DefaultMarkings[index], colors);
 
                     AddBack(category, marking);
                 }
index e803313a10fe679fe828996a369e244447af32e3..4f436bbf408505d54568eade357ceebe90f19741 100644 (file)
@@ -129,7 +129,7 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
         }
 
         humanoid.Species = species;
-        humanoid.MarkingSet.FilterSpecies(species, _markingManager);
+        humanoid.MarkingSet.EnsureSpecies(species, humanoid.SkinColor, _markingManager);
         var oldMarkings = humanoid.MarkingSet.GetForwardEnumerator().ToList();
         humanoid.MarkingSet = new(oldMarkings, prototype.MarkingPoints, _markingManager, _prototypeManager);
 
index 9f8c7d671273978c6bf54a1c736ca56ba5e1a12d..0c0dfc3174fb4584bc003229b13493965b60dc0b 100644 (file)
@@ -42,7 +42,12 @@ public sealed class HumanoidMarkingModifierBaseLayersSetMessage : BoundUserInter
 public sealed class HumanoidMarkingModifierState : BoundUserInterfaceState
 {
     // TODO just use the component state, remove the BUI state altogether.
-    public HumanoidMarkingModifierState(MarkingSet markingSet, string species, Color skinColor, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers)
+    public HumanoidMarkingModifierState(
+        MarkingSet markingSet,
+        string species,
+        Color skinColor,
+        Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers
+    )
     {
         MarkingSet = markingSet;
         Species = species;
@@ -53,5 +58,8 @@ public sealed class HumanoidMarkingModifierState : BoundUserInterfaceState
     public MarkingSet MarkingSet { get; }
     public string Species { get; }
     public Color SkinColor { get; }
+    public Color EyeColor { get; }
+    public Color? HairColor { get; }
+    public Color? FacialHairColor { get; }
     public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers { get; }
 }
index d779d71bd33cedc3b47e6cab5ea3895374439184..de88a17fd773b6376966d4926279d2bbf9506e64 100644 (file)
@@ -3,6 +3,18 @@
   bodyPart: HeadTop
   markingCategory: HeadTop
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:CategoryColoring
+          category: Hair
+      fallbackTypes:
+        - !type:SkinColoring
+    layers:
+      ears_cat_inner:
+        type:
+          !type:SimpleColoring
+            color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/cat_parts.rsi
     state: ears_cat_outer
   bodyPart: Tail
   markingCategory: Tail
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:CategoryColoring
+          category: Hair
+      fallbackTypes:
+        - !type:CategoryColoring
+          category: FacialHair
+        - !type:SkinColoring
   sprites:
   - sprite: Mobs/Customization/cat_parts.rsi
     state: tail_cat
index bf417b7569bab05a1b15c8feb5cb0bb411f8dcdd..0db9994efff4e2bbce71341948c501ca56aed88b 100644 (file)
@@ -3,6 +3,11 @@
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: thorns_head
   bodyPart: Chest
   markingCategory: Chest
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: thorns_body
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: flowers_head
   bodyPart: Chest
   markingCategory: Chest
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: flowers_body
   bodyPart: Chest
   markingCategory: Chest
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SkinColoring
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: leaf_cover
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: bloom
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: bracket
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SkinColoring
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: brush
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: cornflower
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: ficus
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: garland
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: king
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: laurel
   bodyPart: HeadTop
   markingCategory: HeadTop
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: leafy
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: lotus
   bodyPart: HeadTop
   markingCategory: HeadTop
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: meadow
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: oak
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: palm
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: root
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: rose
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: rosey
   bodyPart: HeadTop
   markingCategory: HeadTop
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: shrub
   bodyPart: HeadSide
   markingCategory: HeadSide
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: spinner
   bodyPart: HeadSide
   markingCategory: HeadSide
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: sprout
   bodyPart: HeadTop
   markingCategory: HeadTop
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: vine
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: vinel
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: vines
   bodyPart: Head
   markingCategory: Head
   speciesRestriction: [Diona]
+  coloring:
+    default:
+      type:
+        !type:SimpleColoring
+          color: "#FFFFFF"
   sprites:
   - sprite: Mobs/Customization/diona.rsi
     state: wildflower
index 8c24e82cea4bcbe4578d47221a67ba97cab6375b..81e23f406ffa4acb8084e63ccd2756c878113923 100644 (file)
   id: LizardSnoutRound
   bodyPart: Snout
   markingCategory: Snout
-  followSkinColor: true
+  forcedColoring: true
   speciesRestriction: [Reptilian]
   sprites:
   - sprite: Mobs/Customization/reptilian_parts.rsi
   id: LizardSnoutSharp
   bodyPart: Snout
   markingCategory: Snout
-  followSkinColor: true
+  forcedColoring: true
   speciesRestriction: [Reptilian]
   sprites:
   - sprite: Mobs/Customization/reptilian_parts.rsi
   id: LizardChestTiger
   bodyPart: Chest
   markingCategory: Chest
-  followSkinColor: false
   speciesRestriction: [Reptilian]
   sprites:
   - sprite: Mobs/Customization/reptilian_parts.rsi
-    state: body_tiger
\ No newline at end of file
+    state: body_tiger
index 3b2991134bb03f29a76afaa3bcc8c21b93986fba..dffaa25984a929cbaf1d1adcb5ad4b9db3e646e3 100644 (file)
@@ -3,6 +3,11 @@
   bodyPart: Chest
   markingCategory: Chest
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_hive_chest
   bodyPart: Chest
   markingCategory: Chest
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_nightling
   bodyPart: LLeg
   markingCategory: Legs
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_silverburgh_l_leg
   bodyPart: RLeg
   markingCategory: Legs
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_silverburgh_r_leg
   bodyPart: LArm
   markingCategory: Arms
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_campbell_l_arm
   bodyPart: RArm
   markingCategory: Arms
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_campbell_r_arm
   bodyPart: LLeg
   markingCategory: Legs
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_campbell_l_leg
   bodyPart: RLeg
   markingCategory: Legs
   speciesRestriction: [Human]
+  coloring:
+    default:
+      type:
+        !type:TattooColoring
+      fallbackColor: "#666666"
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_campbell_r_leg
   bodyPart: Eyes
   markingCategory: Head
   speciesRestriction: [Human, SlimePerson, Reptilian]
+  coloring:
+    default:
+      type:
+        !type:EyeColoring
+          negative: true
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_eye_r
   bodyPart: Eyes
   markingCategory: Head
   speciesRestriction: [Human, SlimePerson, Reptilian]
+  coloring:
+    default:
+      type:
+        !type:EyeColoring
+          negative: true
   sprites:
   - sprite: Mobs/Customization/tattoos.rsi
     state: tattoo_eye_l