using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Humanoid.Prototypes; using Robust.Shared.Prototypes; namespace Content.Shared.Humanoid.Markings { public sealed class MarkingManager { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private readonly List _index = new(); public FrozenDictionary> CategorizedMarkings = default!; public FrozenDictionary Markings = default!; public void Initialize() { _prototypeManager.PrototypesReloaded += OnPrototypeReload; CachePrototypes(); } private void CachePrototypes() { _index.Clear(); var markingDict = new Dictionary>(); foreach (var category in Enum.GetValues()) { markingDict.Add(category, new()); } foreach (var prototype in _prototypeManager.EnumeratePrototypes()) { _index.Add(prototype); markingDict[prototype.MarkingCategory].Add(prototype.ID, prototype); } Markings = _prototypeManager.EnumeratePrototypes().ToFrozenDictionary(x => x.ID); CategorizedMarkings = markingDict.ToFrozenDictionary( x => x.Key, x => x.Value.ToFrozenDictionary()); } public FrozenDictionary MarkingsByCategory(MarkingCategories category) { // all marking categories are guaranteed to have a dict entry return CategorizedMarkings[category]; } /// /// Markings by category and species. /// /// /// /// /// This is done per category, as enumerating over every single marking by species isn't useful. /// Please make a pull request if you find a use case for that behavior. /// /// public IReadOnlyDictionary MarkingsByCategoryAndSpecies(MarkingCategories category, string species) { var speciesProto = _prototypeManager.Index(species); var markingPoints = _prototypeManager.Index(speciesProto.MarkingPoints); var res = new Dictionary(); foreach (var (key, marking) in MarkingsByCategory(category)) { if ((markingPoints.OnlyWhitelisted || markingPoints.Points[category].OnlyWhitelisted) && marking.SpeciesRestrictions == null) { continue; } if (marking.SpeciesRestrictions != null && !marking.SpeciesRestrictions.Contains(species)) { continue; } res.Add(key, marking); } return res; } /// /// Markings by category and sex. /// /// /// /// /// This is done per category, as enumerating over every single marking by species isn't useful. /// Please make a pull request if you find a use case for that behavior. /// /// public IReadOnlyDictionary MarkingsByCategoryAndSex(MarkingCategories category, Sex sex) { var res = new Dictionary(); foreach (var (key, marking) in MarkingsByCategory(category)) { if (marking.SexRestriction != null && marking.SexRestriction != sex) { continue; } res.Add(key, marking); } return res; } /// /// Markings by category, species and sex. /// /// /// /// /// /// This is done per category, as enumerating over every single marking by species isn't useful. /// Please make a pull request if you find a use case for that behavior. /// /// public IReadOnlyDictionary MarkingsByCategoryAndSpeciesAndSex(MarkingCategories category, string species, Sex sex) { var speciesProto = _prototypeManager.Index(species); var onlyWhitelisted = _prototypeManager.Index(speciesProto.MarkingPoints).OnlyWhitelisted; var res = new Dictionary(); foreach (var (key, marking) in MarkingsByCategory(category)) { if (onlyWhitelisted && marking.SpeciesRestrictions == null) { continue; } if (marking.SpeciesRestrictions != null && !marking.SpeciesRestrictions.Contains(species)) { continue; } if (marking.SexRestriction != null && marking.SexRestriction != sex) { continue; } res.Add(key, marking); } return res; } public bool TryGetMarking(Marking marking, [NotNullWhen(true)] out MarkingPrototype? markingResult) { return Markings.TryGetValue(marking.MarkingId, out markingResult); } /// /// Check if a marking is valid according to the category, species, and current data this marking has. /// /// /// /// /// /// public bool IsValidMarking(Marking marking, MarkingCategories category, string species, Sex sex) { if (!TryGetMarking(marking, out var proto)) { return false; } if (proto.MarkingCategory != category || proto.SpeciesRestrictions != null && !proto.SpeciesRestrictions.Contains(species) || proto.SexRestriction != null && proto.SexRestriction != sex) { return false; } if (marking.MarkingColors.Count != proto.Sprites.Count) { return false; } return true; } private void OnPrototypeReload(PrototypesReloadedEventArgs args) { if (args.WasModified()) CachePrototypes(); } public bool CanBeApplied(string species, Sex sex, Marking marking, IPrototypeManager? prototypeManager = null) { IoCManager.Resolve(ref prototypeManager); var speciesProto = prototypeManager.Index(species); var onlyWhitelisted = prototypeManager.Index(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; } if (prototype.SexRestriction != null && prototype.SexRestriction != sex) { return false; } return true; } public bool CanBeApplied(string species, Sex sex, MarkingPrototype prototype, IPrototypeManager? prototypeManager = null) { IoCManager.Resolve(ref prototypeManager); var speciesProto = prototypeManager.Index(species); var onlyWhitelisted = prototypeManager.Index(speciesProto.MarkingPoints).OnlyWhitelisted; if (onlyWhitelisted && prototype.SpeciesRestrictions == null) { return false; } if (prototype.SpeciesRestrictions != null && !prototype.SpeciesRestrictions.Contains(species)) { return false; } if (prototype.SexRestriction != null && prototype.SexRestriction != sex) { return false; } return true; } public bool MustMatchSkin(string species, HumanoidVisualLayers layer, out float alpha, IPrototypeManager? prototypeManager = null) { IoCManager.Resolve(ref prototypeManager); var speciesProto = prototypeManager.Index(species); if ( !prototypeManager.Resolve(speciesProto.SpriteSet, out var baseSprites) || !baseSprites.Sprites.TryGetValue(layer, out var spriteName) || !prototypeManager.Resolve(spriteName, out HumanoidSpeciesSpriteLayer? sprite) || sprite == null || !sprite.MarkingsMatchSkin ) { alpha = 1f; return false; } alpha = sprite.LayerAlpha; return true; } } }