using System.Linq; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Serialization; namespace Content.Shared.Humanoid; [DataDefinition] [Serializable, NetSerializable] public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance { public HumanoidCharacterAppearance(string hairStyleId, Color hairColor, string facialHairStyleId, Color facialHairColor, Color eyeColor, Color skinColor, List markings) { HairStyleId = hairStyleId; HairColor = ClampColor(hairColor); FacialHairStyleId = facialHairStyleId; FacialHairColor = ClampColor(facialHairColor); EyeColor = ClampColor(eyeColor); SkinColor = ClampColor(skinColor); Markings = markings; } [DataField("hair")] public string HairStyleId { get; private set; } [DataField("hairColor")] public Color HairColor { get; private set; } [DataField("facialHair")] public string FacialHairStyleId { get; private set; } [DataField("facialHairColor")] public Color FacialHairColor { get; private set; } [DataField("eyeColor")] public Color EyeColor { get; private set; } [DataField("skinColor")] public Color SkinColor { get; private set; } [DataField("markings")] public List Markings { get; private set; } public HumanoidCharacterAppearance WithHairStyleName(string newName) { return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); } public HumanoidCharacterAppearance WithHairColor(Color newColor) { return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); } public HumanoidCharacterAppearance WithFacialHairStyleName(string newName) { return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings); } public HumanoidCharacterAppearance WithFacialHairColor(Color newColor) { return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings); } public HumanoidCharacterAppearance WithEyeColor(Color newColor) { return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings); } public HumanoidCharacterAppearance WithSkinColor(Color newColor) { return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings); } public HumanoidCharacterAppearance WithMarkings(List newMarkings) { return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings); } public HumanoidCharacterAppearance() : this( HairStyles.DefaultHairStyle, Color.Black, HairStyles.DefaultFacialHairStyle, Color.Black, Color.Black, Humanoid.SkinColor.ValidHumanSkinTone, new () ) { } public static HumanoidCharacterAppearance DefaultWithSpecies(string species) { var speciesPrototype = IoCManager.Resolve().Index(species); var skinColor = speciesPrototype.SkinColoration switch { HumanoidSkinColor.HumanToned => Humanoid.SkinColor.HumanSkinTone(speciesPrototype.DefaultHumanSkinTone), HumanoidSkinColor.Hues => speciesPrototype.DefaultSkinTone, HumanoidSkinColor.TintedHues => Humanoid.SkinColor.TintedHues(speciesPrototype.DefaultSkinTone), _ => Humanoid.SkinColor.ValidHumanSkinTone }; return new( HairStyles.DefaultHairStyle, Color.Black, HairStyles.DefaultFacialHairStyle, Color.Black, Color.Black, skinColor, new () ); } private static IReadOnlyList RealisticEyeColors = new List { Color.Brown, Color.Gray, Color.Azure, Color.SteelBlue, Color.Black }; public static HumanoidCharacterAppearance Random(string species, Sex sex) { var random = IoCManager.Resolve(); var markingManager = IoCManager.Resolve(); var hairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, species).Keys.ToList(); var facialHairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, species).Keys.ToList(); var newHairStyle = hairStyles.Count > 0 ? random.Pick(hairStyles) : HairStyles.DefaultHairStyle; var newFacialHairStyle = facialHairStyles.Count == 0 || sex == Sex.Female ? HairStyles.DefaultFacialHairStyle : random.Pick(facialHairStyles); var newHairColor = random.Pick(HairStyles.RealisticHairColors); newHairColor = newHairColor .WithRed(RandomizeColor(newHairColor.R)) .WithGreen(RandomizeColor(newHairColor.G)) .WithBlue(RandomizeColor(newHairColor.B)); // TODO: Add random markings var newEyeColor = random.Pick(RealisticEyeColors); var skinType = IoCManager.Resolve().Index(species).SkinColoration; var newSkinColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1); switch (skinType) { case HumanoidSkinColor.HumanToned: var tone = Math.Round(Humanoid.SkinColor.HumanSkinToneFromColor(newSkinColor)); newSkinColor = Humanoid.SkinColor.HumanSkinTone((int)tone); break; case HumanoidSkinColor.Hues: break; case HumanoidSkinColor.TintedHues: newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor); break; } return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new ()); float RandomizeColor(float channel) { return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f); } } public static Color ClampColor(Color color) { return new(color.RByte, color.GByte, color.BByte); } public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex) { var hairStyleId = appearance.HairStyleId; var facialHairStyleId = appearance.FacialHairStyleId; var hairColor = ClampColor(appearance.HairColor); var facialHairColor = ClampColor(appearance.FacialHairColor); var eyeColor = ClampColor(appearance.EyeColor); var proto = IoCManager.Resolve(); var markingManager = IoCManager.Resolve(); if (!markingManager.MarkingsByCategory(MarkingCategories.Hair).ContainsKey(hairStyleId)) { hairStyleId = HairStyles.DefaultHairStyle; } if (!markingManager.MarkingsByCategory(MarkingCategories.FacialHair).ContainsKey(facialHairStyleId)) { facialHairStyleId = HairStyles.DefaultFacialHairStyle; } var markingSet = new MarkingSet(); var skinColor = appearance.SkinColor; if (proto.TryIndex(species, out SpeciesPrototype? speciesProto)) { markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto); markingSet.EnsureValid(markingManager); if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor)) { skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor); } markingSet.EnsureSpecies(species, skinColor, markingManager); markingSet.EnsureSexes(sex, markingManager); } return new HumanoidCharacterAppearance( hairStyleId, hairColor, facialHairStyleId, facialHairColor, eyeColor, skinColor, markingSet.GetForwardEnumerator().ToList()); } public bool MemberwiseEquals(ICharacterAppearance maybeOther) { if (maybeOther is not HumanoidCharacterAppearance other) return false; if (HairStyleId != other.HairStyleId) return false; if (!HairColor.Equals(other.HairColor)) return false; if (FacialHairStyleId != other.FacialHairStyleId) return false; if (!FacialHairColor.Equals(other.FacialHairColor)) return false; if (!EyeColor.Equals(other.EyeColor)) return false; if (!SkinColor.Equals(other.SkinColor)) return false; if (!Markings.SequenceEqual(other.Markings)) return false; return true; } }