| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 |
- using System.Collections;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using Content.Shared.Humanoid.Prototypes;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Serialization;
- using Robust.Shared.Utility;
- namespace Content.Shared.Humanoid.Markings;
- // the better version of MarkingsSet
- // This one should ensure that a set is valid. Dependency retrieval is
- // probably not a good idea, and any dependency references should last
- // only for the length of a call, and not the lifetime of the set itself.
- //
- // Compared to MarkingsSet, this should allow for server-side authority.
- // Instead of sending the set over, we can instead just send the dictionary
- // and build the set from there. We can also just send a list and rebuild
- // the set without validating points (we're assuming that the server
- /// <summary>
- /// Marking set. For humanoid markings.
- /// </summary>
- /// <remarks>
- /// This is serializable for the admin panel that sets markings on demand for a player.
- /// Most APIs that accept a set of markings usually use a List of type Marking instead.
- /// </remarks>
- [DataDefinition]
- [Serializable, NetSerializable]
- public sealed partial class MarkingSet
- {
- /// <summary>
- /// Every single marking in this set.
- /// </summary>
- /// <remarks>
- /// The original version of MarkingSet preserved ordering across all
- /// markings - this one should instead preserve ordering across all
- /// categories, but not marking categories themselves. This is because
- /// the layers that markings appear in are guaranteed to be in the correct
- /// order. This is here to make lookups slightly faster, even if the n of
- /// a marking set is relatively small, and to encapsulate another important
- /// feature of markings, which is the limit of markings you can put on a
- /// humanoid.
- /// </remarks>
- [DataField("markings")]
- public Dictionary<MarkingCategories, List<Marking>> Markings = new();
- /// <summary>
- /// Marking points for each category.
- /// </summary>
- [DataField("points")]
- public Dictionary<MarkingCategories, MarkingPoints> Points = new();
- public MarkingSet()
- {}
- /// <summary>
- /// Construct a MarkingSet using a list of markings, and a points
- /// dictionary. This will set up the points dictionary, and
- /// process the list, truncating if necessary. Markings that
- /// do not exist as a prototype will be removed.
- /// </summary>
- /// <param name="markings">The lists of markings to use.</param>
- /// <param name="pointsPrototype">The ID of the points dictionary prototype.</param>
- public MarkingSet(List<Marking> markings, string pointsPrototype, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
- {
- IoCManager.Resolve(ref markingManager, ref prototypeManager);
- if (!prototypeManager.TryIndex(pointsPrototype, out MarkingPointsPrototype? points))
- {
- return;
- }
- Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
- foreach (var marking in markings)
- {
- if (!markingManager.TryGetMarking(marking, out var prototype))
- {
- continue;
- }
- AddBack(prototype.MarkingCategory, marking);
- }
- }
- /// <summary>
- /// Construct a MarkingSet using a dictionary of markings,
- /// without point validation. This will still validate every
- /// marking, to ensure that it can be placed into the set.
- /// </summary>
- /// <param name="markings">The list of markings to use.</param>
- public MarkingSet(List<Marking> markings, MarkingManager? markingManager = null)
- {
- IoCManager.Resolve(ref markingManager);
- foreach (var marking in markings)
- {
- if (!markingManager.TryGetMarking(marking, out var prototype))
- {
- continue;
- }
- AddBack(prototype.MarkingCategory, marking);
- }
- }
- /// <summary>
- /// Construct a MarkingSet only with a points dictionary.
- /// </summary>
- /// <param name="pointsPrototype">The ID of the points dictionary prototype.</param>
- public MarkingSet(string pointsPrototype, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
- {
- IoCManager.Resolve(ref markingManager, ref prototypeManager);
- if (!prototypeManager.TryIndex(pointsPrototype, out MarkingPointsPrototype? points))
- {
- return;
- }
- Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
- }
- /// <summary>
- /// Construct a MarkingSet by deep cloning another set.
- /// </summary>
- /// <param name="other">The other marking set.</param>
- public MarkingSet(MarkingSet other)
- {
- foreach (var (key, list) in other.Markings)
- {
- foreach (var marking in list)
- {
- AddBack(key, new(marking));
- }
- }
- Points = MarkingPoints.CloneMarkingPointDictionary(other.Points);
- }
- /// <summary>
- /// Filters and colors markings based on species and it's restrictions in the marking's prototype from this marking set.
- /// </summary>
- /// <param name="species">The species to filter.</param>
- /// <param name="skinColor">The skin color for recoloring (i.e. slimes). Use null if you want only filter markings</param>
- /// <param name="markingManager">Marking manager.</param>
- /// <param name="prototypeManager">Prototype manager.</param>
- public void EnsureSpecies(string species, Color? skinColor, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
- {
- IoCManager.Resolve(ref markingManager);
- IoCManager.Resolve(ref prototypeManager);
- var toRemove = new List<(MarkingCategories category, string id)>();
- var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
- var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
- foreach (var (category, list) in Markings)
- {
- foreach (var marking in list)
- {
- if (!markingManager.TryGetMarking(marking, out var prototype))
- {
- toRemove.Add((category, marking.MarkingId));
- continue;
- }
- if (onlyWhitelisted && prototype.SpeciesRestrictions == null)
- {
- toRemove.Add((category, marking.MarkingId));
- }
- if (prototype.SpeciesRestrictions != null
- && !prototype.SpeciesRestrictions.Contains(species))
- {
- toRemove.Add((category, marking.MarkingId));
- }
- }
- }
- foreach (var remove in toRemove)
- {
- Remove(remove.category, remove.id);
- }
- // Re-color left markings them into skin color if needed (i.e. for slimes)
- if (skinColor != null)
- {
- foreach (var (category, list) in Markings)
- {
- foreach (var marking in list)
- {
- if (markingManager.TryGetMarking(marking, out var prototype) &&
- markingManager.MustMatchSkin(species, prototype.BodyPart, out var alpha, prototypeManager))
- {
- marking.SetColor(skinColor.Value.WithAlpha(alpha));
- }
- }
- }
- }
- }
- /// <summary>
- /// Filters markings based on sex and it's restrictions in the marking's prototype from this marking set.
- /// </summary>
- /// <param name="sex">The species to filter.</param>
- /// <param name="markingManager">Marking manager.</param>
- public void EnsureSexes(Sex sex, MarkingManager? markingManager = null)
- {
- IoCManager.Resolve(ref markingManager);
- var toRemove = new List<(MarkingCategories category, string id)>();
- foreach (var (category, list) in Markings)
- {
- foreach (var marking in list)
- {
- if (!markingManager.TryGetMarking(marking, out var prototype))
- {
- toRemove.Add((category, marking.MarkingId));
- continue;
- }
- if (prototype.SexRestriction != null && prototype.SexRestriction != sex)
- {
- toRemove.Add((category, marking.MarkingId));
- }
- }
- }
- foreach (var remove in toRemove)
- {
- Remove(remove.category, remove.id);
- }
- }
- /// <summary>
- /// Ensures that all markings in this set are valid.
- /// </summary>
- /// <param name="markingManager">Marking manager.</param>
- public void EnsureValid(MarkingManager? markingManager = null)
- {
- IoCManager.Resolve(ref markingManager);
- var toRemove = new List<int>();
- foreach (var (category, list) in Markings)
- {
- for (var i = 0; i < list.Count; i++)
- {
- if (!markingManager.TryGetMarking(list[i], out var marking))
- {
- toRemove.Add(i);
- continue;
- }
- if (marking.Sprites.Count != list[i].MarkingColors.Count)
- {
- list[i] = new Marking(marking.ID, marking.Sprites.Count);
- }
- }
- foreach (var i in toRemove)
- {
- Remove(category, i);
- }
- }
- }
- /// <summary>
- /// Ensures that the default markings as defined by the marking point set in this marking set are applied.
- /// </summary>
- /// <param name="skinColor">Skin color for marking coloring.</param>
- /// <param name="eyeColor">Eye color for marking coloring.</param>
- /// <param name="hairColor">Hair color for marking coloring.</param>
- /// <param name="markingManager">Marking manager.</param>
- public void EnsureDefault(Color? skinColor = null, Color? eyeColor = null, MarkingManager? markingManager = null)
- {
- IoCManager.Resolve(ref markingManager);
- foreach (var (category, points) in Points)
- {
- if (points.Points <= 0 || points.DefaultMarkings.Count <= 0)
- {
- continue;
- }
- var index = 0;
- while (points.Points > 0 || index < points.DefaultMarkings.Count)
- {
- if (markingManager.Markings.TryGetValue(points.DefaultMarkings[index], out var prototype))
- {
- var colors = MarkingColoring.GetMarkingLayerColors(
- prototype,
- skinColor,
- eyeColor,
- this
- );
- var marking = new Marking(points.DefaultMarkings[index], colors);
- AddBack(category, marking);
- }
- index++;
- }
- }
- }
- /// <summary>
- /// How many points are left in this marking set's category
- /// </summary>
- /// <param name="category">The category to check</param>
- /// <returns>A number equal or greater than zero if the category exists, -1 otherwise.</returns>
- public int PointsLeft(MarkingCategories category)
- {
- if (!Points.TryGetValue(category, out var points))
- {
- return -1;
- }
- return points.Points;
- }
- /// <summary>
- /// Add a marking to the front of the category's list of markings.
- /// </summary>
- /// <param name="category">Category to add the marking to.</param>
- /// <param name="marking">The marking instance in question.</param>
- public void AddFront(MarkingCategories category, Marking marking)
- {
- if (!marking.Forced && Points.TryGetValue(category, out var points))
- {
- if (points.Points <= 0)
- {
- return;
- }
- points.Points--;
- }
- if (!Markings.TryGetValue(category, out var markings))
- {
- markings = new();
- Markings[category] = markings;
- }
- markings.Insert(0, marking);
- }
- /// <summary>
- /// Add a marking to the back of the category's list of markings.
- /// </summary>
- /// <param name="category"></param>
- /// <param name="marking"></param>
- public void AddBack(MarkingCategories category, Marking marking)
- {
- if (!marking.Forced && Points.TryGetValue(category, out var points))
- {
- if (points.Points <= 0)
- {
- return;
- }
- points.Points--;
- }
- if (!Markings.TryGetValue(category, out var markings))
- {
- markings = new();
- Markings[category] = markings;
- }
- markings.Add(marking);
- }
- /// <summary>
- /// Adds a category to this marking set.
- /// </summary>
- /// <param name="category"></param>
- /// <returns></returns>
- public List<Marking> AddCategory(MarkingCategories category)
- {
- var markings = new List<Marking>();
- Markings.Add(category, markings);
- return markings;
- }
- /// <summary>
- /// Replace a marking at a given index in a marking category with another marking.
- /// </summary>
- /// <param name="category">The category to replace the marking in.</param>
- /// <param name="index">The index of the marking.</param>
- /// <param name="marking">The marking to insert.</param>
- public void Replace(MarkingCategories category, int index, Marking marking)
- {
- if (index < 0 || !Markings.TryGetValue(category, out var markings)
- || index >= markings.Count)
- {
- return;
- }
- markings[index] = marking;
- }
- /// <summary>
- /// Remove a marking by category and ID.
- /// </summary>
- /// <param name="category">The category that contains the marking.</param>
- /// <param name="id">The marking's ID.</param>
- /// <returns>True if removed, false otherwise.</returns>
- public bool Remove(MarkingCategories category, string id)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return false;
- }
- for (var i = 0; i < markings.Count; i++)
- {
- if (markings[i].MarkingId != id)
- {
- continue;
- }
- if (!markings[i].Forced && Points.TryGetValue(category, out var points))
- {
- points.Points++;
- }
- markings.RemoveAt(i);
- return true;
- }
- return false;
- }
- /// <summary>
- /// Remove a marking by category and index.
- /// </summary>
- /// <param name="category">The category that contains the marking.</param>
- /// <param name="idx">The marking's index.</param>
- /// <returns>True if removed, false otherwise.</returns>
- public void Remove(MarkingCategories category, int idx)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return;
- }
- if (idx < 0 || idx >= markings.Count)
- {
- return;
- }
- if (!markings[idx].Forced && Points.TryGetValue(category, out var points))
- {
- points.Points++;
- }
- markings.RemoveAt(idx);
- }
- /// <summary>
- /// Remove an entire category from this marking set.
- /// </summary>
- /// <param name="category">The category to remove.</param>
- /// <returns>True if removed, false otherwise.</returns>
- public bool RemoveCategory(MarkingCategories category)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return false;
- }
- if (Points.TryGetValue(category, out var points))
- {
- foreach (var marking in markings)
- {
- if (marking.Forced)
- {
- continue;
- }
- points.Points++;
- }
- }
- Markings.Remove(category);
- return true;
- }
- /// <summary>
- /// Clears all markings from this marking set.
- /// </summary>
- public void Clear()
- {
- foreach (var category in Enum.GetValues<MarkingCategories>())
- {
- RemoveCategory(category);
- }
- }
- /// <summary>
- /// Attempt to find the index of a marking in a category by ID.
- /// </summary>
- /// <param name="category">The category to search in.</param>
- /// <param name="id">The ID to search for.</param>
- /// <returns>The index of the marking, otherwise a negative number.</returns>
- public int FindIndexOf(MarkingCategories category, string id)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return -1;
- }
- return markings.FindIndex(m => m.MarkingId == id);
- }
- /// <summary>
- /// Tries to get an entire category from this marking set.
- /// </summary>
- /// <param name="category">The category to fetch.</param>
- /// <param name="markings">A read only list of the all markings in that category.</param>
- /// <returns>True if successful, false otherwise.</returns>
- public bool TryGetCategory(MarkingCategories category, [NotNullWhen(true)] out IReadOnlyList<Marking>? markings)
- {
- markings = null;
- if (Markings.TryGetValue(category, out var list))
- {
- markings = list;
- return true;
- }
- return false;
- }
- /// <summary>
- /// Tries to get a marking from this marking set, by category.
- /// </summary>
- /// <param name="category">The category to search in.</param>
- /// <param name="id">The ID to search for.</param>
- /// <param name="marking">The marking, if it was retrieved.</param>
- /// <returns>True if successful, false otherwise.</returns>
- public bool TryGetMarking(MarkingCategories category, string id, [NotNullWhen(true)] out Marking? marking)
- {
- marking = null;
- if (!Markings.TryGetValue(category, out var markings))
- {
- return false;
- }
- foreach (var m in markings)
- {
- if (m.MarkingId == id)
- {
- marking = m;
- return true;
- }
- }
- return false;
- }
- /// <summary>
- /// Shifts a marking's rank towards the front of the list
- /// </summary>
- /// <param name="category">The category to shift in.</param>
- /// <param name="idx">Index of the marking.</param>
- public void ShiftRankUp(MarkingCategories category, int idx)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return;
- }
- if (idx < 0 || idx >= markings.Count || idx - 1 < 0)
- {
- return;
- }
- (markings[idx - 1], markings[idx]) = (markings[idx], markings[idx - 1]);
- }
- /// <summary>
- /// Shifts a marking's rank upwards from the end of the list
- /// </summary>
- /// <param name="category">The category to shift in.</param>
- /// <param name="idx">Index of the marking from the end</param>
- public void ShiftRankUpFromEnd(MarkingCategories category, int idx)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return;
- }
- ShiftRankUp(category, markings.Count - idx - 1);
- }
- /// <summary>
- /// Shifts a marking's rank towards the end of the list
- /// </summary>
- /// <param name="category">The category to shift in.</param>
- /// <param name="idx">Index of the marking.</param>
- public void ShiftRankDown(MarkingCategories category, int idx)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return;
- }
- if (idx < 0 || idx >= markings.Count || idx + 1 >= markings.Count)
- {
- return;
- }
- (markings[idx + 1], markings[idx]) = (markings[idx], markings[idx + 1]);
- }
- /// <summary>
- /// Shifts a marking's rank downwards from the end of the list
- /// </summary>
- /// <param name="category">The category to shift in.</param>
- /// <param name="idx">Index of the marking from the end</param>
- public void ShiftRankDownFromEnd(MarkingCategories category, int idx)
- {
- if (!Markings.TryGetValue(category, out var markings))
- {
- return;
- }
- ShiftRankDown(category, markings.Count - idx - 1);
- }
- /// <summary>
- /// Gets all markings in this set as an enumerator. Lists will be organized, but categories may be in any order.
- /// </summary>
- /// <returns>An enumerator of <see cref="Marking"/>s.</returns>
- public ForwardMarkingEnumerator GetForwardEnumerator()
- {
- var markings = new List<Marking>();
- foreach (var (_, list) in Markings)
- {
- markings.AddRange(list);
- }
- return new ForwardMarkingEnumerator(markings);
- }
- /// <summary>
- /// Gets an enumerator of markings in this set, but only for one category.
- /// </summary>
- /// <param name="category">The category to fetch.</param>
- /// <returns>An enumerator of <see cref="Marking"/>s in that category.</returns>
- public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category)
- {
- var markings = new List<Marking>();
- if (Markings.TryGetValue(category, out var listing))
- {
- markings = new(listing);
- }
- return new ForwardMarkingEnumerator(markings);
- }
- /// <summary>
- /// Gets all markings in this set as an enumerator, but in reverse order. Lists will be in reverse order, but categories may be in any order.
- /// </summary>
- /// <returns>An enumerator of <see cref="Marking"/>s in reverse.</returns>
- public ReverseMarkingEnumerator GetReverseEnumerator()
- {
- var markings = new List<Marking>();
- foreach (var (_, list) in Markings)
- {
- markings.AddRange(list);
- }
- return new ReverseMarkingEnumerator(markings);
- }
- /// <summary>
- /// Gets an enumerator of markings in this set in reverse order, but only for one category.
- /// </summary>
- /// <param name="category">The category to fetch.</param>
- /// <returns>An enumerator of <see cref="Marking"/>s in that category, in reverse order.</returns>
- public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category)
- {
- var markings = new List<Marking>();
- if (Markings.TryGetValue(category, out var listing))
- {
- markings = new(listing);
- }
- return new ReverseMarkingEnumerator(markings);
- }
- public bool CategoryEquals(MarkingCategories category, MarkingSet other)
- {
- if (!Markings.TryGetValue(category, out var markings)
- || !other.Markings.TryGetValue(category, out var markingsOther))
- {
- return false;
- }
- return markings.SequenceEqual(markingsOther);
- }
- public bool Equals(MarkingSet other)
- {
- foreach (var (category, _) in Markings)
- {
- if (!CategoryEquals(category, other))
- {
- return false;
- }
- }
- return true;
- }
- /// <summary>
- /// Gets a difference of marking categories between two marking sets
- /// </summary>
- /// <param name="other">The other marking set.</param>
- /// <returns>Enumerator of marking categories that were different between the two.</returns>
- public IEnumerable<MarkingCategories> CategoryDifference(MarkingSet other)
- {
- foreach (var (category, _) in Markings)
- {
- if (!CategoryEquals(category, other))
- {
- yield return category;
- }
- }
- }
- }
- public sealed class ForwardMarkingEnumerator : IEnumerable<Marking>
- {
- private List<Marking> _markings;
- public ForwardMarkingEnumerator(List<Marking> markings)
- {
- _markings = markings;
- }
- public IEnumerator<Marking> GetEnumerator()
- {
- return new MarkingsEnumerator(_markings, false);
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
- public sealed class ReverseMarkingEnumerator : IEnumerable<Marking>
- {
- private List<Marking> _markings;
- public ReverseMarkingEnumerator(List<Marking> markings)
- {
- _markings = markings;
- }
- public IEnumerator<Marking> GetEnumerator()
- {
- return new MarkingsEnumerator(_markings, true);
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- }
- public sealed class MarkingsEnumerator : IEnumerator<Marking>
- {
- private List<Marking> _markings;
- private bool _reverse;
- int position;
- public MarkingsEnumerator(List<Marking> markings, bool reverse)
- {
- _markings = markings;
- _reverse = reverse;
- if (_reverse)
- {
- position = _markings.Count;
- }
- else
- {
- position = -1;
- }
- }
- public bool MoveNext()
- {
- if (_reverse)
- {
- position--;
- return (position >= 0);
- }
- else
- {
- position++;
- return (position < _markings.Count);
- }
- }
- public void Reset()
- {
- if (_reverse)
- {
- position = _markings.Count;
- }
- else
- {
- position = -1;
- }
- }
- public void Dispose()
- {}
- object IEnumerator.Current
- {
- get => _markings[position];
- }
- public Marking Current
- {
- get => _markings[position];
- }
- }
|