Marking default coloring (#13039)
* 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
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Humanoid
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
147
Content.Shared/Humanoid/Markings/MarkingColoring.cs
Normal file
147
Content.Shared/Humanoid/Markings/MarkingColoring.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user