MarkingManager.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. using System.Collections.Frozen;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Linq;
  4. using Content.Shared.Humanoid.Prototypes;
  5. using Robust.Shared.Prototypes;
  6. namespace Content.Shared.Humanoid.Markings
  7. {
  8. public sealed class MarkingManager
  9. {
  10. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  11. private readonly List<MarkingPrototype> _index = new();
  12. public FrozenDictionary<MarkingCategories, FrozenDictionary<string, MarkingPrototype>> CategorizedMarkings = default!;
  13. public FrozenDictionary<string, MarkingPrototype> Markings = default!;
  14. public void Initialize()
  15. {
  16. _prototypeManager.PrototypesReloaded += OnPrototypeReload;
  17. CachePrototypes();
  18. }
  19. private void CachePrototypes()
  20. {
  21. _index.Clear();
  22. var markingDict = new Dictionary<MarkingCategories, Dictionary<string, MarkingPrototype>>();
  23. foreach (var category in Enum.GetValues<MarkingCategories>())
  24. {
  25. markingDict.Add(category, new());
  26. }
  27. foreach (var prototype in _prototypeManager.EnumeratePrototypes<MarkingPrototype>())
  28. {
  29. _index.Add(prototype);
  30. markingDict[prototype.MarkingCategory].Add(prototype.ID, prototype);
  31. }
  32. Markings = _prototypeManager.EnumeratePrototypes<MarkingPrototype>().ToFrozenDictionary(x => x.ID);
  33. CategorizedMarkings = markingDict.ToFrozenDictionary(
  34. x => x.Key,
  35. x => x.Value.ToFrozenDictionary());
  36. }
  37. public FrozenDictionary<string, MarkingPrototype> MarkingsByCategory(MarkingCategories category)
  38. {
  39. // all marking categories are guaranteed to have a dict entry
  40. return CategorizedMarkings[category];
  41. }
  42. /// <summary>
  43. /// Markings by category and species.
  44. /// </summary>
  45. /// <param name="category"></param>
  46. /// <param name="species"></param>
  47. /// <remarks>
  48. /// This is done per category, as enumerating over every single marking by species isn't useful.
  49. /// Please make a pull request if you find a use case for that behavior.
  50. /// </remarks>
  51. /// <returns></returns>
  52. public IReadOnlyDictionary<string, MarkingPrototype> MarkingsByCategoryAndSpecies(MarkingCategories category,
  53. string species)
  54. {
  55. var speciesProto = _prototypeManager.Index<SpeciesPrototype>(species);
  56. var onlyWhitelisted = _prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
  57. var res = new Dictionary<string, MarkingPrototype>();
  58. foreach (var (key, marking) in MarkingsByCategory(category))
  59. {
  60. if (onlyWhitelisted && marking.SpeciesRestrictions == null)
  61. {
  62. continue;
  63. }
  64. if (marking.SpeciesRestrictions != null && !marking.SpeciesRestrictions.Contains(species))
  65. {
  66. continue;
  67. }
  68. res.Add(key, marking);
  69. }
  70. return res;
  71. }
  72. /// <summary>
  73. /// Markings by category and sex.
  74. /// </summary>
  75. /// <param name="category"></param>
  76. /// <param name="sex"></param>
  77. /// <remarks>
  78. /// This is done per category, as enumerating over every single marking by species isn't useful.
  79. /// Please make a pull request if you find a use case for that behavior.
  80. /// </remarks>
  81. /// <returns></returns>
  82. public IReadOnlyDictionary<string, MarkingPrototype> MarkingsByCategoryAndSex(MarkingCategories category,
  83. Sex sex)
  84. {
  85. var res = new Dictionary<string, MarkingPrototype>();
  86. foreach (var (key, marking) in MarkingsByCategory(category))
  87. {
  88. if (marking.SexRestriction != null && marking.SexRestriction != sex)
  89. {
  90. continue;
  91. }
  92. res.Add(key, marking);
  93. }
  94. return res;
  95. }
  96. /// <summary>
  97. /// Markings by category, species and sex.
  98. /// </summary>
  99. /// <param name="category"></param>
  100. /// <param name="species"></param>
  101. /// <param name="sex"></param>
  102. /// <remarks>
  103. /// This is done per category, as enumerating over every single marking by species isn't useful.
  104. /// Please make a pull request if you find a use case for that behavior.
  105. /// </remarks>
  106. /// <returns></returns>
  107. public IReadOnlyDictionary<string, MarkingPrototype> MarkingsByCategoryAndSpeciesAndSex(MarkingCategories category,
  108. string species, Sex sex)
  109. {
  110. var speciesProto = _prototypeManager.Index<SpeciesPrototype>(species);
  111. var onlyWhitelisted = _prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
  112. var res = new Dictionary<string, MarkingPrototype>();
  113. foreach (var (key, marking) in MarkingsByCategory(category))
  114. {
  115. if (onlyWhitelisted && marking.SpeciesRestrictions == null)
  116. {
  117. continue;
  118. }
  119. if (marking.SpeciesRestrictions != null && !marking.SpeciesRestrictions.Contains(species))
  120. {
  121. continue;
  122. }
  123. if (marking.SexRestriction != null && marking.SexRestriction != sex)
  124. {
  125. continue;
  126. }
  127. res.Add(key, marking);
  128. }
  129. return res;
  130. }
  131. public bool TryGetMarking(Marking marking, [NotNullWhen(true)] out MarkingPrototype? markingResult)
  132. {
  133. return Markings.TryGetValue(marking.MarkingId, out markingResult);
  134. }
  135. /// <summary>
  136. /// Check if a marking is valid according to the category, species, and current data this marking has.
  137. /// </summary>
  138. /// <param name="marking"></param>
  139. /// <param name="category"></param>
  140. /// <param name="species"></param>
  141. /// <param name="sex"></param>
  142. /// <returns></returns>
  143. public bool IsValidMarking(Marking marking, MarkingCategories category, string species, Sex sex)
  144. {
  145. if (!TryGetMarking(marking, out var proto))
  146. {
  147. return false;
  148. }
  149. if (proto.MarkingCategory != category ||
  150. proto.SpeciesRestrictions != null && !proto.SpeciesRestrictions.Contains(species) ||
  151. proto.SexRestriction != null && proto.SexRestriction != sex)
  152. {
  153. return false;
  154. }
  155. if (marking.MarkingColors.Count != proto.Sprites.Count)
  156. {
  157. return false;
  158. }
  159. return true;
  160. }
  161. private void OnPrototypeReload(PrototypesReloadedEventArgs args)
  162. {
  163. if (args.WasModified<MarkingPrototype>())
  164. CachePrototypes();
  165. }
  166. public bool CanBeApplied(string species, Sex sex, Marking marking, IPrototypeManager? prototypeManager = null)
  167. {
  168. IoCManager.Resolve(ref prototypeManager);
  169. var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
  170. var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
  171. if (!TryGetMarking(marking, out var prototype))
  172. {
  173. return false;
  174. }
  175. if (onlyWhitelisted && prototype.SpeciesRestrictions == null)
  176. {
  177. return false;
  178. }
  179. if (prototype.SpeciesRestrictions != null
  180. && !prototype.SpeciesRestrictions.Contains(species))
  181. {
  182. return false;
  183. }
  184. if (prototype.SexRestriction != null && prototype.SexRestriction != sex)
  185. {
  186. return false;
  187. }
  188. return true;
  189. }
  190. public bool CanBeApplied(string species, Sex sex, MarkingPrototype prototype, IPrototypeManager? prototypeManager = null)
  191. {
  192. IoCManager.Resolve(ref prototypeManager);
  193. var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
  194. var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
  195. if (onlyWhitelisted && prototype.SpeciesRestrictions == null)
  196. {
  197. return false;
  198. }
  199. if (prototype.SpeciesRestrictions != null &&
  200. !prototype.SpeciesRestrictions.Contains(species))
  201. {
  202. return false;
  203. }
  204. if (prototype.SexRestriction != null && prototype.SexRestriction != sex)
  205. {
  206. return false;
  207. }
  208. return true;
  209. }
  210. public bool MustMatchSkin(string species, HumanoidVisualLayers layer, out float alpha, IPrototypeManager? prototypeManager = null)
  211. {
  212. IoCManager.Resolve(ref prototypeManager);
  213. var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
  214. if (
  215. !prototypeManager.TryIndex(speciesProto.SpriteSet, out HumanoidSpeciesBaseSpritesPrototype? baseSprites) ||
  216. !baseSprites.Sprites.TryGetValue(layer, out var spriteName) ||
  217. !prototypeManager.TryIndex(spriteName, out HumanoidSpeciesSpriteLayer? sprite) ||
  218. sprite == null ||
  219. !sprite.MarkingsMatchSkin
  220. )
  221. {
  222. alpha = 1f;
  223. return false;
  224. }
  225. alpha = sprite.LayerAlpha;
  226. return true;
  227. }
  228. }
  229. }