Markings (#7072)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
105
Content.Shared/Markings/Marking.cs
Normal file
105
Content.Shared/Markings/Marking.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class Marking : IEquatable<Marking>, IComparable<Marking>, IComparable<string>
|
||||
{
|
||||
private List<Color> _markingColors = new();
|
||||
|
||||
private Marking(string markingId,
|
||||
List<Color> markingColors)
|
||||
{
|
||||
MarkingId = markingId;
|
||||
_markingColors = markingColors;
|
||||
}
|
||||
|
||||
public Marking(string markingId,
|
||||
IReadOnlyList<Color> markingColors)
|
||||
: this(markingId, new List<Color>(markingColors))
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
public Marking(string markingId)
|
||||
: this(markingId, new List<Color>())
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
public Marking(string markingId, int colorCount)
|
||||
{
|
||||
MarkingId = markingId;
|
||||
List<Color> colors = new();
|
||||
for (int i = 0; i < colorCount; i++)
|
||||
colors.Add(Color.White);
|
||||
_markingColors = colors;
|
||||
}
|
||||
|
||||
[DataField("markingId")]
|
||||
[ViewVariables]
|
||||
public string MarkingId { get; } = default!;
|
||||
|
||||
[DataField("markingColor")]
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<Color> MarkingColors => _markingColors;
|
||||
|
||||
public void SetColor(int colorIndex, Color color) =>
|
||||
_markingColors[colorIndex] = color;
|
||||
|
||||
public int CompareTo(Marking? marking)
|
||||
{
|
||||
if (marking == null) return 1;
|
||||
else return this.MarkingId.CompareTo(marking.MarkingId);
|
||||
}
|
||||
|
||||
public int CompareTo(string? markingId)
|
||||
{
|
||||
if (markingId == null) return 1;
|
||||
return this.MarkingId.CompareTo(markingId);
|
||||
}
|
||||
|
||||
public bool Equals(Marking? other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
return (this.MarkingId.Equals(other.MarkingId));
|
||||
}
|
||||
|
||||
|
||||
// look this could be better but I don't think serializing
|
||||
// colors is the correct thing to do
|
||||
//
|
||||
// this is still janky imo but serializing a color and feeding
|
||||
// it into the default JSON serializer (which is just *fine*)
|
||||
// doesn't seem to have compatible interfaces? this 'works'
|
||||
// for now but should eventually be improved so that this can,
|
||||
// in fact just be serialized through a convenient interface
|
||||
new public string ToString()
|
||||
{
|
||||
// reserved character
|
||||
string sanitizedName = this.MarkingId.Replace('@', '_');
|
||||
List<string> colorStringList = new();
|
||||
foreach (Color color in _markingColors)
|
||||
colorStringList.Add(color.ToHex());
|
||||
|
||||
return $"{sanitizedName}@{String.Join(',', colorStringList)}";
|
||||
}
|
||||
|
||||
public static Marking? ParseFromDbString(string input)
|
||||
{
|
||||
if (input.Length == 0) return null;
|
||||
var split = input.Split('@');
|
||||
if (split.Length != 2) return null;
|
||||
List<Color> colorList = new();
|
||||
foreach (string color in split[1].Split(','))
|
||||
colorList.Add(Color.FromHex(color));
|
||||
|
||||
return new Marking(split[0], colorList);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Content.Shared/Markings/MarkingCategories.cs
Normal file
19
Content.Shared/Markings/MarkingCategories.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum MarkingCategories : byte
|
||||
{
|
||||
Head,
|
||||
HeadTop,
|
||||
HeadSide,
|
||||
Snout,
|
||||
Chest,
|
||||
Arms,
|
||||
Legs,
|
||||
Tail,
|
||||
Overlay
|
||||
}
|
||||
}
|
||||
68
Content.Shared/Markings/MarkingManager.cs
Normal file
68
Content.Shared/Markings/MarkingManager.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
public sealed class MarkingManager
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly List<MarkingPrototype> _index = new();
|
||||
private readonly Dictionary<MarkingCategories, List<MarkingPrototype>> _markingDict = new();
|
||||
private readonly Dictionary<string, MarkingPrototype> _markings = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_prototypeManager.PrototypesReloaded += OnPrototypeReload;
|
||||
|
||||
foreach (var category in Enum.GetValues<MarkingCategories>())
|
||||
_markingDict.Add(category, new List<MarkingPrototype>());
|
||||
|
||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<MarkingPrototype>())
|
||||
{
|
||||
_index.Add(prototype);
|
||||
_markingDict[prototype.MarkingCategory].Add(prototype);
|
||||
_markings.Add(prototype.ID, prototype);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, MarkingPrototype> Markings() => _markings;
|
||||
public IReadOnlyDictionary<MarkingCategories, List<MarkingPrototype>> CategorizedMarkings() => _markingDict;
|
||||
|
||||
public IReadOnlyDictionary<MarkingCategories, List<MarkingPrototype>> MarkingsBySpecies(string species)
|
||||
{
|
||||
var result = new Dictionary<MarkingCategories, List<MarkingPrototype>>(_markingDict);
|
||||
|
||||
foreach (var list in result.Values)
|
||||
{
|
||||
list.RemoveAll(marking => marking.SpeciesRestrictions != null && marking.SpeciesRestrictions.Contains(species));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsValidMarking(Marking marking, [NotNullWhen(true)] out MarkingPrototype? markingResult)
|
||||
{
|
||||
return _markings.TryGetValue(marking.MarkingId, out markingResult);
|
||||
}
|
||||
|
||||
private void OnPrototypeReload(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
if(!args.ByType.TryGetValue(typeof(MarkingPrototype), out var set))
|
||||
return;
|
||||
|
||||
|
||||
_index.RemoveAll(i => set.Modified.ContainsKey(i.ID));
|
||||
|
||||
foreach (var prototype in set.Modified.Values)
|
||||
{
|
||||
var markingPrototype = (MarkingPrototype) prototype;
|
||||
_index.Add(markingPrototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Content.Shared/Markings/MarkingPrototype.cs
Normal file
39
Content.Shared/Markings/MarkingPrototype.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[Prototype("marking")]
|
||||
public sealed class MarkingPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; } = "uwu";
|
||||
|
||||
public string Name { get; private set; } = default!;
|
||||
|
||||
[DataField("bodyPart", required: true)]
|
||||
public HumanoidVisualLayers BodyPart { get; } = default!;
|
||||
|
||||
[DataField("markingCategory", required: true)]
|
||||
public MarkingCategories MarkingCategory { get; } = default!;
|
||||
|
||||
[DataField("speciesRestriction")]
|
||||
public List<string>? SpeciesRestrictions { get; }
|
||||
|
||||
[DataField("followSkinColor")]
|
||||
public bool FollowSkinColor { get; } = false;
|
||||
|
||||
[DataField("sprites", required: true)]
|
||||
public List<SpriteSpecifier> Sprites { get; private set; } = default!;
|
||||
|
||||
public Marking AsMarking()
|
||||
{
|
||||
return new Marking(ID, Sprites.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Content.Shared/Markings/MarkingsComponent.cs
Normal file
58
Content.Shared/Markings/MarkingsComponent.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.CharacterAppearance;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Shared.Markings
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class MarkingsComponent : Component
|
||||
{
|
||||
public Dictionary<HumanoidVisualLayers, List<Marking>> ActiveMarkings = new();
|
||||
|
||||
// Layer points for the attached mob. This is verified client side (but should be verified server side, eventually as well),
|
||||
// but upon render for the given entity with this component, it will start subtracting
|
||||
// points from this set. Upon depletion, no more sprites in this layer will be
|
||||
// rendered. If an entry is null, however, it is considered 'unlimited points' for
|
||||
// that layer.
|
||||
//
|
||||
// Layer points are useful for restricting the amount of markings a specific layer can use
|
||||
// for specific mobs (i.e., a lizard should only use one set of horns and maybe two frills),
|
||||
// and all species with selectable tails should have exactly one tail)
|
||||
//
|
||||
// If something is required, then something must be selected in that category. Otherwise,
|
||||
// the first instance of a marking in that category will be added to a character
|
||||
// upon round start.
|
||||
[DataField("layerPoints")]
|
||||
public Dictionary<MarkingCategories, MarkingPoints> LayerPoints = new();
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed class MarkingPoints
|
||||
{
|
||||
[DataField("points", required: true)]
|
||||
public int Points = 0;
|
||||
[DataField("required", required: true)]
|
||||
public bool Required = false;
|
||||
// Default markings for this layer.
|
||||
[DataField("defaultMarkings")]
|
||||
public List<string> DefaultMarkings = new();
|
||||
|
||||
public static Dictionary<MarkingCategories, MarkingPoints> CloneMarkingPointDictionary(Dictionary<MarkingCategories, MarkingPoints> self)
|
||||
{
|
||||
var clone = new Dictionary<MarkingCategories, MarkingPoints>();
|
||||
|
||||
foreach (var (category, points) in self)
|
||||
{
|
||||
clone[category] = new MarkingPoints()
|
||||
{
|
||||
Points = points.Points,
|
||||
Required = points.Required,
|
||||
DefaultMarkings = points.DefaultMarkings
|
||||
};
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
283
Content.Shared/Markings/MarkingsSet.cs
Normal file
283
Content.Shared/Markings/MarkingsSet.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Markings;
|
||||
|
||||
// TODO: Maybe put points logic into here too? It would make some sense
|
||||
// but it would have to be a template loaded in, otherwise clients could
|
||||
// send really invalid points sets that would have to be verified on the
|
||||
// server/client every time
|
||||
//
|
||||
// currently marking point constraints are just validated upon data send
|
||||
// from the client's UI (which might be a nono, means that connections
|
||||
// can just send garbage marking sets and it will save on the server)
|
||||
// and when an entity is rendered, (which is OK enough)
|
||||
//
|
||||
// equally, we'd need to access references every time we wanted to get
|
||||
// the managers required, which... not *terrible* if we do null default
|
||||
// params
|
||||
[Serializable, NetSerializable]
|
||||
public class MarkingsSet : IEnumerable, IEquatable<MarkingsSet>
|
||||
{
|
||||
// if you want a rust style VecDeque, you're looking at
|
||||
// the wrong place, i just wanted a similar API + some
|
||||
// markings specific functions
|
||||
private List<Marking> _markings = new();
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => _markings.Count;
|
||||
}
|
||||
|
||||
public MarkingsSet()
|
||||
{
|
||||
}
|
||||
|
||||
public MarkingsSet(List<Marking> markings)
|
||||
{
|
||||
_markings = markings;
|
||||
}
|
||||
|
||||
public MarkingsSet(MarkingsSet other)
|
||||
{
|
||||
_markings = new(other._markings);
|
||||
}
|
||||
|
||||
public Marking this[int idx] => Index(idx);
|
||||
|
||||
public Marking Index(int idx)
|
||||
{
|
||||
return _markings[idx];
|
||||
}
|
||||
|
||||
// Gets a marking idx spaces from the back of the list.
|
||||
public Marking IndexReverse(int idx)
|
||||
{
|
||||
return _markings[_markings.Count - 1 - idx];
|
||||
}
|
||||
|
||||
public void AddFront(Marking marking)
|
||||
{
|
||||
_markings.Insert(0, marking);
|
||||
}
|
||||
|
||||
public void AddBack(Marking marking)
|
||||
{
|
||||
_markings.Add(marking);
|
||||
}
|
||||
|
||||
public bool Remove(Marking marking)
|
||||
{
|
||||
return _markings.Remove(marking);
|
||||
}
|
||||
|
||||
public bool Contains(Marking marking)
|
||||
{
|
||||
return _markings.Contains(marking);
|
||||
}
|
||||
|
||||
public int FindIndexOf(string id)
|
||||
{
|
||||
return _markings.FindIndex(m => m.MarkingId == id);
|
||||
}
|
||||
|
||||
// Shifts a marking's rank upwards (i.e., towards the front of the list)
|
||||
public void ShiftRankUp(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= _markings.Count || idx - 1 < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = _markings[idx - 1];
|
||||
_markings[idx - 1] = _markings[idx];
|
||||
_markings[idx] = temp;
|
||||
}
|
||||
|
||||
// Shifts up from the back (i.e., 2nd position from end)
|
||||
public void ShiftRankUpFromEnd(int idx)
|
||||
{
|
||||
ShiftRankUp(Count - idx - 1);
|
||||
}
|
||||
|
||||
// Ditto, but the opposite direction.
|
||||
public void ShiftRankDown(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= _markings.Count || idx + 1 >= _markings.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = _markings[idx + 1];
|
||||
_markings[idx + 1] = _markings[idx];
|
||||
_markings[idx] = temp;
|
||||
}
|
||||
|
||||
// Ditto as above.
|
||||
public void ShiftRankDownFromEnd(int idx)
|
||||
{
|
||||
ShiftRankDown(Count - idx - 1);
|
||||
}
|
||||
|
||||
// Ensures that all markings in a set are valid.
|
||||
public static MarkingsSet EnsureValid(MarkingsSet set, MarkingManager? manager = null)
|
||||
{
|
||||
if (manager == null)
|
||||
{
|
||||
manager = IoCManager.Resolve<MarkingManager>();
|
||||
}
|
||||
|
||||
var newList = set._markings.Where(marking => manager.Markings().ContainsKey(marking.MarkingId)).ToList();
|
||||
|
||||
set._markings = newList;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
// Filters out markings based on species.
|
||||
public static MarkingsSet FilterSpecies(MarkingsSet set, string species)
|
||||
{
|
||||
var _markingsManager = IoCManager.Resolve<MarkingManager>();
|
||||
var newList = set._markings.Where(marking =>
|
||||
{
|
||||
if (!_markingsManager.Markings().TryGetValue(marking.MarkingId, out MarkingPrototype? prototype))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prototype.SpeciesRestrictions != null)
|
||||
{
|
||||
if (!prototype.SpeciesRestrictions.Contains(species))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}).ToList();
|
||||
|
||||
set._markings = newList;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
// Processes a MarkingsSet using the given dictionary of MarkingPoints.
|
||||
public static MarkingsSet ProcessPoints(MarkingsSet set, Dictionary<MarkingCategories, MarkingPoints> points)
|
||||
{
|
||||
var finalSet = new List<Marking>();
|
||||
var _markingsManager = IoCManager.Resolve<MarkingManager>();
|
||||
|
||||
foreach (var marking in set)
|
||||
{
|
||||
if (_markingsManager.Markings().TryGetValue(marking.MarkingId, out MarkingPrototype? markingPrototype))
|
||||
{
|
||||
if (points.TryGetValue(markingPrototype.MarkingCategory, out var pointsRemaining))
|
||||
{
|
||||
if (pointsRemaining.Points == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
pointsRemaining.Points--;
|
||||
|
||||
finalSet.Add(marking);
|
||||
}
|
||||
else
|
||||
{
|
||||
// points don't exist otherwise
|
||||
finalSet.Add(marking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set._markings = finalSet;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (IEnumerator) GetEnumerator();
|
||||
}
|
||||
|
||||
public MarkingsEnumerator GetEnumerator()
|
||||
{
|
||||
return new MarkingsEnumerator(_markings, false);
|
||||
}
|
||||
|
||||
public IEnumerator GetReverseEnumerator()
|
||||
{
|
||||
return (IEnumerator) new MarkingsEnumerator(_markings, true);
|
||||
}
|
||||
|
||||
public bool Equals(MarkingsSet? set)
|
||||
{
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _markings.SequenceEqual(set._markings);
|
||||
}
|
||||
}
|
||||
|
||||
public class MarkingsEnumerator : IEnumerator
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get => _markings[position];
|
||||
}
|
||||
|
||||
public Marking Current
|
||||
{
|
||||
get => _markings[position];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user