* Fix usages of TryIndex()
Most usages of TryIndex() were using it incorrectly. Checking whether prototype IDs specified in prototypes actually existed before using them. This is not appropriate as it's just hiding bugs that should be getting caught by the YAML linter and other tools. (#39115)
This then resulted in TryIndex() getting modified to log errors (94f98073b0), which is incorrect as it causes false-positive errors in proper uses of the API: external data validation. (#39098)
This commit goes through and checks every call site of TryIndex() to see whether they were correct. Most call sites were replaced with the new Resolve(), which is suitable for these "defensive programming" use cases.
Fixes #39115
Breaking change: while doing this I noticed IdCardComponent and related systems were erroneously using ProtoId<AccessLevelPrototype> for job prototypes. This has been corrected.
* fix tests
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
273 lines
9.7 KiB
C#
273 lines
9.7 KiB
C#
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<MarkingPrototype> _index = new();
|
|
public FrozenDictionary<MarkingCategories, FrozenDictionary<string, MarkingPrototype>> CategorizedMarkings = default!;
|
|
public FrozenDictionary<string, MarkingPrototype> Markings = default!;
|
|
|
|
public void Initialize()
|
|
{
|
|
_prototypeManager.PrototypesReloaded += OnPrototypeReload;
|
|
CachePrototypes();
|
|
}
|
|
|
|
private void CachePrototypes()
|
|
{
|
|
_index.Clear();
|
|
var markingDict = new Dictionary<MarkingCategories, Dictionary<string, MarkingPrototype>>();
|
|
|
|
foreach (var category in Enum.GetValues<MarkingCategories>())
|
|
{
|
|
markingDict.Add(category, new());
|
|
}
|
|
|
|
foreach (var prototype in _prototypeManager.EnumeratePrototypes<MarkingPrototype>())
|
|
{
|
|
_index.Add(prototype);
|
|
markingDict[prototype.MarkingCategory].Add(prototype.ID, prototype);
|
|
}
|
|
|
|
Markings = _prototypeManager.EnumeratePrototypes<MarkingPrototype>().ToFrozenDictionary(x => x.ID);
|
|
CategorizedMarkings = markingDict.ToFrozenDictionary(
|
|
x => x.Key,
|
|
x => x.Value.ToFrozenDictionary());
|
|
}
|
|
|
|
public FrozenDictionary<string, MarkingPrototype> MarkingsByCategory(MarkingCategories category)
|
|
{
|
|
// all marking categories are guaranteed to have a dict entry
|
|
return CategorizedMarkings[category];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Markings by category and species.
|
|
/// </summary>
|
|
/// <param name="category"></param>
|
|
/// <param name="species"></param>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns></returns>
|
|
public IReadOnlyDictionary<string, MarkingPrototype> MarkingsByCategoryAndSpecies(MarkingCategories category,
|
|
string species)
|
|
{
|
|
var speciesProto = _prototypeManager.Index<SpeciesPrototype>(species);
|
|
var markingPoints = _prototypeManager.Index(speciesProto.MarkingPoints);
|
|
var res = new Dictionary<string, MarkingPrototype>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Markings by category and sex.
|
|
/// </summary>
|
|
/// <param name="category"></param>
|
|
/// <param name="sex"></param>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns></returns>
|
|
public IReadOnlyDictionary<string, MarkingPrototype> MarkingsByCategoryAndSex(MarkingCategories category,
|
|
Sex sex)
|
|
{
|
|
var res = new Dictionary<string, MarkingPrototype>();
|
|
|
|
foreach (var (key, marking) in MarkingsByCategory(category))
|
|
{
|
|
if (marking.SexRestriction != null && marking.SexRestriction != sex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
res.Add(key, marking);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Markings by category, species and sex.
|
|
/// </summary>
|
|
/// <param name="category"></param>
|
|
/// <param name="species"></param>
|
|
/// <param name="sex"></param>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <returns></returns>
|
|
public IReadOnlyDictionary<string, MarkingPrototype> MarkingsByCategoryAndSpeciesAndSex(MarkingCategories category,
|
|
string species, Sex sex)
|
|
{
|
|
var speciesProto = _prototypeManager.Index<SpeciesPrototype>(species);
|
|
var onlyWhitelisted = _prototypeManager.Index(speciesProto.MarkingPoints).OnlyWhitelisted;
|
|
var res = new Dictionary<string, MarkingPrototype>();
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a marking is valid according to the category, species, and current data this marking has.
|
|
/// </summary>
|
|
/// <param name="marking"></param>
|
|
/// <param name="category"></param>
|
|
/// <param name="species"></param>
|
|
/// <param name="sex"></param>
|
|
/// <returns></returns>
|
|
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<MarkingPrototype>())
|
|
CachePrototypes();
|
|
}
|
|
|
|
public bool CanBeApplied(string species, Sex sex, Marking marking, IPrototypeManager? prototypeManager = null)
|
|
{
|
|
IoCManager.Resolve(ref prototypeManager);
|
|
|
|
var speciesProto = prototypeManager.Index<SpeciesPrototype>(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<SpeciesPrototype>(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<SpeciesPrototype>(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;
|
|
}
|
|
}
|
|
}
|