HumanoidCharacterAppearance.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System.Linq;
  2. using Content.Shared.Humanoid.Markings;
  3. using Content.Shared.Humanoid.Prototypes;
  4. using Robust.Shared.Prototypes;
  5. using Robust.Shared.Random;
  6. using Robust.Shared.Serialization;
  7. namespace Content.Shared.Humanoid;
  8. [DataDefinition]
  9. [Serializable, NetSerializable]
  10. public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, IEquatable<HumanoidCharacterAppearance>
  11. {
  12. [DataField("hair")]
  13. public string HairStyleId { get; set; } = HairStyles.DefaultHairStyle;
  14. [DataField]
  15. public Color HairColor { get; set; } = Color.Black;
  16. [DataField("facialHair")]
  17. public string FacialHairStyleId { get; set; } = HairStyles.DefaultFacialHairStyle;
  18. [DataField]
  19. public Color FacialHairColor { get; set; } = Color.Black;
  20. [DataField]
  21. public Color EyeColor { get; set; } = Color.Black;
  22. [DataField]
  23. public Color SkinColor { get; set; } = Humanoid.SkinColor.ValidHumanSkinTone;
  24. [DataField]
  25. public List<Marking> Markings { get; set; } = new();
  26. public HumanoidCharacterAppearance(string hairStyleId,
  27. Color hairColor,
  28. string facialHairStyleId,
  29. Color facialHairColor,
  30. Color eyeColor,
  31. Color skinColor,
  32. List<Marking> markings)
  33. {
  34. HairStyleId = hairStyleId;
  35. HairColor = ClampColor(hairColor);
  36. FacialHairStyleId = facialHairStyleId;
  37. FacialHairColor = ClampColor(facialHairColor);
  38. EyeColor = ClampColor(eyeColor);
  39. SkinColor = ClampColor(skinColor);
  40. Markings = markings;
  41. }
  42. public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) :
  43. this(other.HairStyleId, other.HairColor, other.FacialHairStyleId, other.FacialHairColor, other.EyeColor, other.SkinColor, new(other.Markings))
  44. {
  45. }
  46. public HumanoidCharacterAppearance WithHairStyleName(string newName)
  47. {
  48. return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings);
  49. }
  50. public HumanoidCharacterAppearance WithHairColor(Color newColor)
  51. {
  52. return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings);
  53. }
  54. public HumanoidCharacterAppearance WithFacialHairStyleName(string newName)
  55. {
  56. return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings);
  57. }
  58. public HumanoidCharacterAppearance WithFacialHairColor(Color newColor)
  59. {
  60. return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings);
  61. }
  62. public HumanoidCharacterAppearance WithEyeColor(Color newColor)
  63. {
  64. return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings);
  65. }
  66. public HumanoidCharacterAppearance WithSkinColor(Color newColor)
  67. {
  68. return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings);
  69. }
  70. public HumanoidCharacterAppearance WithMarkings(List<Marking> newMarkings)
  71. {
  72. return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings);
  73. }
  74. public static HumanoidCharacterAppearance DefaultWithSpecies(string species)
  75. {
  76. var speciesPrototype = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species);
  77. var skinColor = speciesPrototype.SkinColoration switch
  78. {
  79. HumanoidSkinColor.HumanToned => Humanoid.SkinColor.HumanSkinTone(speciesPrototype.DefaultHumanSkinTone),
  80. HumanoidSkinColor.Hues => speciesPrototype.DefaultSkinTone,
  81. HumanoidSkinColor.TintedHues => Humanoid.SkinColor.TintedHues(speciesPrototype.DefaultSkinTone),
  82. HumanoidSkinColor.VoxFeathers => Humanoid.SkinColor.ClosestVoxColor(speciesPrototype.DefaultSkinTone),
  83. _ => Humanoid.SkinColor.ValidHumanSkinTone,
  84. };
  85. return new(
  86. HairStyles.DefaultHairStyle,
  87. Color.Black,
  88. HairStyles.DefaultFacialHairStyle,
  89. Color.Black,
  90. Color.Black,
  91. skinColor,
  92. new ()
  93. );
  94. }
  95. private static IReadOnlyList<Color> RealisticEyeColors = new List<Color>
  96. {
  97. Color.Brown,
  98. Color.Gray,
  99. Color.Azure,
  100. Color.SteelBlue,
  101. Color.Black
  102. };
  103. public static HumanoidCharacterAppearance Random(string species, Sex sex)
  104. {
  105. var random = IoCManager.Resolve<IRobustRandom>();
  106. var markingManager = IoCManager.Resolve<MarkingManager>();
  107. var hairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, species).Keys.ToList();
  108. var facialHairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, species).Keys.ToList();
  109. var newHairStyle = hairStyles.Count > 0
  110. ? random.Pick(hairStyles)
  111. : HairStyles.DefaultHairStyle;
  112. var newFacialHairStyle = facialHairStyles.Count == 0 || sex == Sex.Female
  113. ? HairStyles.DefaultFacialHairStyle
  114. : random.Pick(facialHairStyles);
  115. var newHairColor = random.Pick(HairStyles.RealisticHairColors);
  116. newHairColor = newHairColor
  117. .WithRed(RandomizeColor(newHairColor.R))
  118. .WithGreen(RandomizeColor(newHairColor.G))
  119. .WithBlue(RandomizeColor(newHairColor.B));
  120. // TODO: Add random markings
  121. var newEyeColor = random.Pick(RealisticEyeColors);
  122. var skinType = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species).SkinColoration;
  123. var newSkinColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1);
  124. switch (skinType)
  125. {
  126. case HumanoidSkinColor.HumanToned:
  127. var tone = Math.Round(Humanoid.SkinColor.HumanSkinToneFromColor(newSkinColor));
  128. newSkinColor = Humanoid.SkinColor.HumanSkinTone((int)tone);
  129. break;
  130. case HumanoidSkinColor.Hues:
  131. break;
  132. case HumanoidSkinColor.TintedHues:
  133. newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor);
  134. break;
  135. case HumanoidSkinColor.VoxFeathers:
  136. newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(newSkinColor);
  137. break;
  138. }
  139. return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new ());
  140. float RandomizeColor(float channel)
  141. {
  142. return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f);
  143. }
  144. }
  145. public static Color ClampColor(Color color)
  146. {
  147. return new(color.RByte, color.GByte, color.BByte);
  148. }
  149. public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex)
  150. {
  151. var hairStyleId = appearance.HairStyleId;
  152. var facialHairStyleId = appearance.FacialHairStyleId;
  153. var hairColor = ClampColor(appearance.HairColor);
  154. var facialHairColor = ClampColor(appearance.FacialHairColor);
  155. var eyeColor = ClampColor(appearance.EyeColor);
  156. var proto = IoCManager.Resolve<IPrototypeManager>();
  157. var markingManager = IoCManager.Resolve<MarkingManager>();
  158. if (!markingManager.MarkingsByCategory(MarkingCategories.Hair).ContainsKey(hairStyleId))
  159. {
  160. hairStyleId = HairStyles.DefaultHairStyle;
  161. }
  162. if (!markingManager.MarkingsByCategory(MarkingCategories.FacialHair).ContainsKey(facialHairStyleId))
  163. {
  164. facialHairStyleId = HairStyles.DefaultFacialHairStyle;
  165. }
  166. var markingSet = new MarkingSet();
  167. var skinColor = appearance.SkinColor;
  168. if (proto.TryIndex(species, out SpeciesPrototype? speciesProto))
  169. {
  170. markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto);
  171. markingSet.EnsureValid(markingManager);
  172. if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor))
  173. {
  174. skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor);
  175. }
  176. markingSet.EnsureSpecies(species, skinColor, markingManager);
  177. markingSet.EnsureSexes(sex, markingManager);
  178. }
  179. return new HumanoidCharacterAppearance(
  180. hairStyleId,
  181. hairColor,
  182. facialHairStyleId,
  183. facialHairColor,
  184. eyeColor,
  185. skinColor,
  186. markingSet.GetForwardEnumerator().ToList());
  187. }
  188. public bool MemberwiseEquals(ICharacterAppearance maybeOther)
  189. {
  190. if (maybeOther is not HumanoidCharacterAppearance other) return false;
  191. if (HairStyleId != other.HairStyleId) return false;
  192. if (!HairColor.Equals(other.HairColor)) return false;
  193. if (FacialHairStyleId != other.FacialHairStyleId) return false;
  194. if (!FacialHairColor.Equals(other.FacialHairColor)) return false;
  195. if (!EyeColor.Equals(other.EyeColor)) return false;
  196. if (!SkinColor.Equals(other.SkinColor)) return false;
  197. if (!Markings.SequenceEqual(other.Markings)) return false;
  198. return true;
  199. }
  200. public bool Equals(HumanoidCharacterAppearance? other)
  201. {
  202. if (ReferenceEquals(null, other)) return false;
  203. if (ReferenceEquals(this, other)) return true;
  204. return HairStyleId == other.HairStyleId &&
  205. HairColor.Equals(other.HairColor) &&
  206. FacialHairStyleId == other.FacialHairStyleId &&
  207. FacialHairColor.Equals(other.FacialHairColor) &&
  208. EyeColor.Equals(other.EyeColor) &&
  209. SkinColor.Equals(other.SkinColor) &&
  210. Markings.SequenceEqual(other.Markings);
  211. }
  212. public override bool Equals(object? obj)
  213. {
  214. return ReferenceEquals(this, obj) || obj is HumanoidCharacterAppearance other && Equals(other);
  215. }
  216. public override int GetHashCode()
  217. {
  218. return HashCode.Combine(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings);
  219. }
  220. public HumanoidCharacterAppearance Clone()
  221. {
  222. return new(this);
  223. }
  224. }