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, IEquatable { [DataField("hair")] public string HairStyleId { get; set; } = HairStyles.DefaultHairStyle; [DataField] public Color HairColor { get; set; } = Color.Black; [DataField("facialHair")] public string FacialHairStyleId { get; set; } = HairStyles.DefaultFacialHairStyle; [DataField] public Color FacialHairColor { get; set; } = Color.Black; [DataField] public Color EyeColor { get; set; } = Color.Black; [DataField] public Color SkinColor { get; set; } = Humanoid.SkinColor.ValidHumanSkinTone; [DataField] public List Markings { get; set; } = new(); 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; } public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) : this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings)) { } 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 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), HumanoidSkinColor.VoxFeathers => Humanoid.SkinColor.ClosestVoxColor(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; case HumanoidSkinColor.VoxFeathers: newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(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; } public bool Equals(HumanoidCharacterAppearance? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return HairStyleId == other.HairStyleId && HairColor.Equals(other.HairColor) && FacialHairStyleId == other.FacialHairStyleId && FacialHairColor.Equals(other.FacialHairColor) && EyeColor.Equals(other.EyeColor) && SkinColor.Equals(other.SkinColor) && Markings.SequenceEqual(other.Markings); } public override bool Equals(object? obj) { return ReferenceEquals(this, obj) || obj is HumanoidCharacterAppearance other && Equals(other); } public override int GetHashCode() { return HashCode.Combine(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); } public HumanoidCharacterAppearance Clone() { return new(this); } }