Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Flipp Syder
2022-05-05 01:07:42 -07:00
committed by GitHub
parent 0263b4b52b
commit a30cae21f6
47 changed files with 3785 additions and 77 deletions

View 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);
}
}
}

View 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
}
}

View 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);
}
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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];
}
}