+using System.Linq;
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
component.PermanentlyHidden = new(state.PermanentlyHidden);
component.CustomBaseLayers = state.CustomBaseLayers.ShallowClone();
+
UpdateLayers(component, sprite);
ApplyMarkingSet(uid, state.Markings, component, sprite);
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());
spriteComp.RemoveLayer(index);
}
}
-
private void ApplyMarking(EntityUid uid,
MarkingPrototype markingPrototype,
IReadOnlyList<Color>? colors,
}
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);
}
}
}
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))
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
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();
}
}
- 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;
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()
{
if (!IgnoreSpecies)
{
- _currentMarkings.FilterSpecies(_currentSpecies, _markingManager);
+ _currentMarkings.EnsureSpecies(_currentSpecies, null, _markingManager);
}
// walk backwards through the list for visual purposes
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(species);
_currentMarkings = new(markingList, speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
- _currentMarkings.FilterSpecies(species);
+ _currentMarkings.EnsureSpecies(species, null, _markingManager);
Populate();
PopulateUsed();
_selectedMarking = CMarkingsUsed[item.ItemIndex];
var prototype = (MarkingPrototype) _selectedMarking.Metadata!;
- if (prototype.FollowSkinColor)
+ if (prototype.ForcedColoring)
{
CMarkingColors.Visible = false;
}
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;
private readonly IEntityManager _entMan;
private readonly IConfigurationManager _configurationManager;
private readonly MarkingManager _markingManager;
-
+
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
private LineEdit _flavorTextEdit = null!;
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
+ UpdateCMarkingsHair();
IsDirty = true;
};
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
+ UpdateCMarkingsFacialHair();
IsDirty = true;
};
Profile.Appearance.WithHairStyleName(HairStyles.DefaultHairStyle)
);
UpdateHairPickers();
+ UpdateCMarkingsHair();
IsDirty = true;
};
Profile.Appearance.WithFacialHairStyleName(HairStyles.DefaultFacialHairStyle)
);
UpdateHairPickers();
+ UpdateCMarkingsFacialHair();
IsDirty = true;
};
);
UpdateHairPickers();
-
+ UpdateCMarkingsHair();
IsDirty = true;
};
);
UpdateHairPickers();
-
+ UpdateCMarkingsFacialHair();
IsDirty = true;
};
return;
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithEyeColor(newColor));
+ CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
IsDirty = true;
};
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:
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
- _needUpdatePreview = true;
UpdateControls();
+ _needUpdatePreview = true;
}
private void SetAge(int newAge)
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()
{
return;
}
-
var hairMarking = Profile.Appearance.HairStyleId switch
{
HairStyles.DefaultHairStyle => new List<Marking>(),
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)
return;
}
+ CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
_eyesPicker.SetData(Profile.Appearance.EyeColor);
}
UpdateClothingControls();
UpdateBackpackControls();
UpdateAgeEdit();
- UpdateHairPickers();
UpdateEyePickers();
UpdateSaveButton();
UpdateJobPriorities();
UpdateTraitPreferences();
UpdateMarkings();
RebuildSpriteView();
+ UpdateHairPickers();
+ UpdateCMarkingsHair();
+ UpdateCMarkingsFacialHair();
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
}
LoadProfile(uid, startingSet.Profile, humanoid);
+
}
private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, ExaminedEvent args)
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;
{
return;
}
-
- humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, _markingManager);
+ humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, humanoid.EyeColor, _markingManager);
}
}
_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
+ ));
}
});
}
_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
+ ));
}
}
_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
+ ));
}
}
UpdateInterface(uid, args.User, args.Session);
}
-}
+}
\ No newline at end of file
[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]
{
markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto);
markingSet.EnsureValid(markingManager);
- markingSet.FilterSpecies(species, markingManager);
switch (speciesProto.SkinColoration)
{
break;
}
+ markingSet.EnsureSpecies(species, skinColor, markingManager);
}
return new HumanoidCharacterAppearance(
-using Robust.Shared.Serialization;
+using Content.Shared.Humanoid.Markings;
+using Robust.Shared.Serialization;
namespace Content.Shared.Humanoid
{
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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);
+ }
+}
using System.Linq;
+using Content.Shared.Humanoid.Prototypes;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
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)
--- /dev/null
+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.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;
+ }
}
}
[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!;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
namespace Content.Shared.Humanoid.Markings;
}
}
+ /// <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>
}
/// <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);
{
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>
/// <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);
{
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);
}
}
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);
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;
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; }
}
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
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
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
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