Makes humanoid appearance component networked. (#13009)
Fixes https://github.com/space-wizards/space-station-14/issues/12248
This commit is contained in:
136
Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
Normal file
136
Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed class HumanoidAppearanceComponent : Component
|
||||
{
|
||||
[DataField("markingSet")]
|
||||
public MarkingSet MarkingSet = new();
|
||||
|
||||
[DataField("baseLayers")]
|
||||
public Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> BaseLayers = new();
|
||||
|
||||
[DataField("permanentlyHidden")]
|
||||
public HashSet<HumanoidVisualLayers> PermanentlyHidden = new();
|
||||
|
||||
// Couldn't these be somewhere else?
|
||||
|
||||
[DataField("gender")]
|
||||
[ViewVariables] public Gender Gender = default!;
|
||||
|
||||
[DataField("age")]
|
||||
[ViewVariables] public int Age = 18;
|
||||
|
||||
/// <summary>
|
||||
/// Any custom base layers this humanoid might have. See:
|
||||
/// limb transplants (potentially), robotic arms, etc.
|
||||
/// Stored on the server, this is merged in the client into
|
||||
/// all layer settings.
|
||||
/// </summary>
|
||||
[DataField("customBaseLayers")]
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Current species. Dictates things like base body sprites,
|
||||
/// base humanoid to spawn, etc.
|
||||
/// </summary>
|
||||
[DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer<SpeciesPrototype>))]
|
||||
public string Species { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The initial profile and base layers to apply to this humanoid.
|
||||
/// </summary>
|
||||
[DataField("initial", customTypeSerializer: typeof(PrototypeIdSerializer<HumanoidProfilePrototype>))]
|
||||
public string? Initial { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Skin color of this humanoid.
|
||||
/// </summary>
|
||||
[DataField("skinColor")]
|
||||
public Color SkinColor { get; set; } = Color.FromHex("#C0967F");
|
||||
|
||||
/// <summary>
|
||||
/// Visual layers currently hidden. This will affect the base sprite
|
||||
/// on this humanoid layer, and any markings that sit above it.
|
||||
/// </summary>
|
||||
[DataField("hiddenLayers")]
|
||||
public HashSet<HumanoidVisualLayers> HiddenLayers = new();
|
||||
|
||||
[DataField("sex")]
|
||||
public Sex Sex = Sex.Male;
|
||||
|
||||
[DataField("eyeColor")]
|
||||
public Color EyeColor = Color.Brown;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HumanoidAppearanceState : ComponentState
|
||||
{
|
||||
public readonly MarkingSet Markings;
|
||||
public readonly HashSet<HumanoidVisualLayers> PermanentlyHidden;
|
||||
public readonly HashSet<HumanoidVisualLayers> HiddenLayers;
|
||||
public readonly Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers;
|
||||
public readonly Sex Sex;
|
||||
public readonly Gender Gender;
|
||||
public readonly int Age = 18;
|
||||
public readonly string Species;
|
||||
public readonly Color SkinColor;
|
||||
public readonly Color EyeColor;
|
||||
|
||||
public HumanoidAppearanceState(
|
||||
MarkingSet currentMarkings,
|
||||
HashSet<HumanoidVisualLayers> permanentlyHidden,
|
||||
HashSet<HumanoidVisualLayers> hiddenLayers,
|
||||
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers,
|
||||
Sex sex,
|
||||
Gender gender,
|
||||
int age,
|
||||
string species,
|
||||
Color skinColor,
|
||||
Color eyeColor)
|
||||
{
|
||||
Markings = currentMarkings;
|
||||
PermanentlyHidden = permanentlyHidden;
|
||||
HiddenLayers = hiddenLayers;
|
||||
CustomBaseLayers = customBaseLayers;
|
||||
Sex = sex;
|
||||
Gender = gender;
|
||||
Age = age;
|
||||
Species = species;
|
||||
SkinColor = skinColor;
|
||||
EyeColor = eyeColor;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct CustomBaseLayerInfo
|
||||
{
|
||||
public CustomBaseLayerInfo(string id, Color? color = null)
|
||||
{
|
||||
DebugTools.Assert(IoCManager.Resolve<IPrototypeManager>().HasIndex<HumanoidSpeciesSpriteLayer>(id));
|
||||
ID = id;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ID of this custom base layer. Must be a <see cref="HumanoidSpeciesSpriteLayer"/>.
|
||||
/// </summary>
|
||||
[DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer<HumanoidSpeciesSpriteLayer>), required: true)]
|
||||
public string ID { init; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Color of this custom base layer. Null implies skin colour.
|
||||
/// </summary>
|
||||
[DataField("color")]
|
||||
public Color? Color { init; get; }
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class HumanoidComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Current species. Dictates things like base body sprites,
|
||||
/// base humanoid to spawn, etc.
|
||||
/// </summary>
|
||||
[DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer<SpeciesPrototype>))]
|
||||
public string Species { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The initial profile and base layers to apply to this humanoid.
|
||||
/// </summary>
|
||||
[DataField("initial", customTypeSerializer: typeof(PrototypeIdSerializer<HumanoidProfilePrototype>))]
|
||||
public string? Initial { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Skin color of this humanoid.
|
||||
/// </summary>
|
||||
[DataField("skinColor")]
|
||||
public Color SkinColor { get; set; } = Color.FromHex("#C0967F");
|
||||
|
||||
/// <summary>
|
||||
/// Visual layers currently hidden. This will affect the base sprite
|
||||
/// on this humanoid layer, and any markings that sit above it.
|
||||
/// </summary>
|
||||
[ViewVariables] public readonly HashSet<HumanoidVisualLayers> HiddenLayers = new();
|
||||
|
||||
[DataField("sex")] public Sex Sex = Sex.Male;
|
||||
|
||||
public MarkingSet CurrentMarkings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Any custom base layers this humanoid might have. See:
|
||||
/// limb transplants (potentially), robotic arms, etc.
|
||||
/// Stored on the server, this is merged in the client into
|
||||
/// all layer settings.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayers = new();
|
||||
|
||||
public HashSet<HumanoidVisualLayers> PermanentlyHidden = new();
|
||||
|
||||
public HashSet<HumanoidVisualLayers> AllHiddenLayers
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new HashSet<HumanoidVisualLayers>(HiddenLayers);
|
||||
result.UnionWith(PermanentlyHidden);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't these be somewhere else?
|
||||
[ViewVariables] public Gender Gender = default!;
|
||||
[ViewVariables] public int Age = 18;
|
||||
|
||||
[ViewVariables] public List<Marking> CurrentClientMarkings = new();
|
||||
|
||||
public Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> BaseLayers = new();
|
||||
|
||||
public string LastSpecies = default!;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CustomBaseLayerInfo
|
||||
{
|
||||
public CustomBaseLayerInfo(string id, Color color)
|
||||
{
|
||||
ID = id;
|
||||
Color = color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ID of this custom base layer. Must be a <see cref="HumanoidSpeciesSpriteLayer"/>.
|
||||
/// </summary>
|
||||
[DataField("id")]
|
||||
public string ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Color of this custom base layer.
|
||||
/// </summary>
|
||||
[DataField("color")]
|
||||
public Color Color { get; }
|
||||
}
|
||||
@@ -15,6 +15,14 @@ namespace Content.Shared.Humanoid
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetSexMorph(HumanoidVisualLayers layer, Sex sex, string id)
|
||||
{
|
||||
if (!HasSexMorph(layer) || sex == Sex.Unsexed)
|
||||
return id;
|
||||
|
||||
return $"{id}{sex}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sublayers. Any other layers that may visually depend on this layer existing.
|
||||
/// For example, the head has layers such as eyes, hair, etc. depending on it.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum HumanoidVisualizerKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HumanoidVisualizerData : ICloneable
|
||||
{
|
||||
public HumanoidVisualizerData(string species, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayerInfo, Color skinColor, Sex sex, List<HumanoidVisualLayers> layerVisibility, List<Marking> markings)
|
||||
{
|
||||
Species = species;
|
||||
CustomBaseLayerInfo = customBaseLayerInfo;
|
||||
SkinColor = skinColor;
|
||||
Sex = sex;
|
||||
LayerVisibility = layerVisibility;
|
||||
Markings = markings;
|
||||
}
|
||||
|
||||
public string Species { get; }
|
||||
public Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> CustomBaseLayerInfo { get; }
|
||||
public Color SkinColor { get; }
|
||||
public Sex Sex { get; }
|
||||
public List<HumanoidVisualLayers> LayerVisibility { get; }
|
||||
public List<Marking> Markings { get; }
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new HumanoidVisualizerData(Species, new(CustomBaseLayerInfo), SkinColor, Sex, new(LayerVisibility), new(Markings));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Humanoid.Markings
|
||||
{
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class Marking : IEquatable<Marking>, IComparable<Marking>, IComparable<string>
|
||||
{
|
||||
@@ -54,7 +55,7 @@ namespace Content.Shared.Humanoid.Markings
|
||||
/// <summary>
|
||||
/// If this marking is currently visible.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("visible")]
|
||||
public bool Visible = true;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Content.Shared.Humanoid.Markings;
|
||||
/// 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 class MarkingSet
|
||||
{
|
||||
@@ -40,16 +41,14 @@ public sealed class MarkingSet
|
||||
/// feature of markings, which is the limit of markings you can put on a
|
||||
/// humanoid.
|
||||
/// </remarks>
|
||||
private Dictionary<MarkingCategories, List<Marking>> _markings = new();
|
||||
|
||||
// why i didn't encapsulate this in the first place, i won't know
|
||||
[DataField("markings")]
|
||||
public Dictionary<MarkingCategories, List<Marking>> Markings = new();
|
||||
|
||||
/// <summary>
|
||||
/// Marking points for each category.
|
||||
/// </summary>
|
||||
private Dictionary<MarkingCategories, MarkingPoints> _points = new();
|
||||
|
||||
public IReadOnlyList<Marking> this[MarkingCategories category] => _markings[category];
|
||||
[DataField("points")]
|
||||
public Dictionary<MarkingCategories, MarkingPoints> Points = new();
|
||||
|
||||
public MarkingSet()
|
||||
{}
|
||||
@@ -71,7 +70,7 @@ public sealed class MarkingSet
|
||||
return;
|
||||
}
|
||||
|
||||
_points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
|
||||
Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
|
||||
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
@@ -111,7 +110,7 @@ public sealed class MarkingSet
|
||||
/// <param name="other">The other marking set.</param>
|
||||
public MarkingSet(MarkingSet other)
|
||||
{
|
||||
foreach (var (key, list) in other._markings)
|
||||
foreach (var (key, list) in other.Markings)
|
||||
{
|
||||
foreach (var marking in list)
|
||||
{
|
||||
@@ -119,7 +118,7 @@ public sealed class MarkingSet
|
||||
}
|
||||
}
|
||||
|
||||
_points = MarkingPoints.CloneMarkingPointDictionary(other._points);
|
||||
Points = MarkingPoints.CloneMarkingPointDictionary(other.Points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,7 +136,7 @@ public sealed class MarkingSet
|
||||
var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
|
||||
var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted;
|
||||
|
||||
foreach (var (category, list) in _markings)
|
||||
foreach (var (category, list) in Markings)
|
||||
{
|
||||
foreach (var marking in list)
|
||||
{
|
||||
@@ -175,7 +174,7 @@ public sealed class MarkingSet
|
||||
IoCManager.Resolve(ref markingManager);
|
||||
|
||||
var toRemove = new List<int>();
|
||||
foreach (var (category, list) in _markings)
|
||||
foreach (var (category, list) in Markings)
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
@@ -207,7 +206,7 @@ public sealed class MarkingSet
|
||||
{
|
||||
IoCManager.Resolve(ref markingManager);
|
||||
|
||||
foreach (var (category, points) in _points)
|
||||
foreach (var (category, points) in Points)
|
||||
{
|
||||
if (points.Points <= 0 || points.DefaultMarkings.Count <= 0)
|
||||
{
|
||||
@@ -251,7 +250,7 @@ public sealed class MarkingSet
|
||||
/// <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))
|
||||
if (!Points.TryGetValue(category, out var points))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -266,7 +265,7 @@ public sealed class MarkingSet
|
||||
/// <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 (!marking.Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
if (points.Points <= 0)
|
||||
{
|
||||
@@ -276,10 +275,10 @@ public sealed class MarkingSet
|
||||
points.Points--;
|
||||
}
|
||||
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
markings = new();
|
||||
_markings[category] = markings;
|
||||
Markings[category] = markings;
|
||||
}
|
||||
|
||||
markings.Insert(0, marking);
|
||||
@@ -292,7 +291,7 @@ public sealed class MarkingSet
|
||||
/// <param name="marking"></param>
|
||||
public void AddBack(MarkingCategories category, Marking marking)
|
||||
{
|
||||
if (!marking.Forced && _points.TryGetValue(category, out var points))
|
||||
if (!marking.Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
if (points.Points <= 0)
|
||||
{
|
||||
@@ -302,10 +301,10 @@ public sealed class MarkingSet
|
||||
points.Points--;
|
||||
}
|
||||
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
markings = new();
|
||||
_markings[category] = markings;
|
||||
Markings[category] = markings;
|
||||
}
|
||||
|
||||
|
||||
@@ -320,7 +319,7 @@ public sealed class MarkingSet
|
||||
public List<Marking> AddCategory(MarkingCategories category)
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
_markings.Add(category, markings);
|
||||
Markings.Add(category, markings);
|
||||
return markings;
|
||||
}
|
||||
|
||||
@@ -332,7 +331,7 @@ public sealed class MarkingSet
|
||||
/// <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)
|
||||
if (index < 0 || !Markings.TryGetValue(category, out var markings)
|
||||
|| index >= markings.Count)
|
||||
{
|
||||
return;
|
||||
@@ -349,7 +348,7 @@ public sealed class MarkingSet
|
||||
/// <returns>True if removed, false otherwise.</returns>
|
||||
public bool Remove(MarkingCategories category, string id)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -361,7 +360,7 @@ public sealed class MarkingSet
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!markings[i].Forced && _points.TryGetValue(category, out var points))
|
||||
if (!markings[i].Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
points.Points++;
|
||||
}
|
||||
@@ -381,7 +380,7 @@ public sealed class MarkingSet
|
||||
/// <returns>True if removed, false otherwise.</returns>
|
||||
public void Remove(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -391,7 +390,7 @@ public sealed class MarkingSet
|
||||
return;
|
||||
}
|
||||
|
||||
if (!markings[idx].Forced && _points.TryGetValue(category, out var points))
|
||||
if (!markings[idx].Forced && Points.TryGetValue(category, out var points))
|
||||
{
|
||||
points.Points++;
|
||||
}
|
||||
@@ -406,12 +405,12 @@ public sealed class MarkingSet
|
||||
/// <returns>True if removed, false otherwise.</returns>
|
||||
public bool RemoveCategory(MarkingCategories category)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_points.TryGetValue(category, out var points))
|
||||
if (Points.TryGetValue(category, out var points))
|
||||
{
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
@@ -424,7 +423,7 @@ public sealed class MarkingSet
|
||||
}
|
||||
}
|
||||
|
||||
_markings.Remove(category);
|
||||
Markings.Remove(category);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -447,7 +446,7 @@ public sealed class MarkingSet
|
||||
/// <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))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@@ -465,7 +464,7 @@ public sealed class MarkingSet
|
||||
{
|
||||
markings = null;
|
||||
|
||||
if (_markings.TryGetValue(category, out var list))
|
||||
if (Markings.TryGetValue(category, out var list))
|
||||
{
|
||||
markings = list;
|
||||
return true;
|
||||
@@ -485,7 +484,7 @@ public sealed class MarkingSet
|
||||
{
|
||||
marking = null;
|
||||
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -509,7 +508,7 @@ public sealed class MarkingSet
|
||||
/// <param name="idx">Index of the marking.</param>
|
||||
public void ShiftRankUp(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -529,7 +528,7 @@ public sealed class MarkingSet
|
||||
/// <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))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -544,7 +543,7 @@ public sealed class MarkingSet
|
||||
/// <param name="idx">Index of the marking.</param>
|
||||
public void ShiftRankDown(MarkingCategories category, int idx)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -564,7 +563,7 @@ public sealed class MarkingSet
|
||||
/// <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))
|
||||
if (!Markings.TryGetValue(category, out var markings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -579,7 +578,7 @@ public sealed class MarkingSet
|
||||
public ForwardMarkingEnumerator GetForwardEnumerator()
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
foreach (var (_, list) in _markings)
|
||||
foreach (var (_, list) in Markings)
|
||||
{
|
||||
markings.AddRange(list);
|
||||
}
|
||||
@@ -595,7 +594,7 @@ public sealed class MarkingSet
|
||||
public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category)
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
if (_markings.TryGetValue(category, out var listing))
|
||||
if (Markings.TryGetValue(category, out var listing))
|
||||
{
|
||||
markings = new(listing);
|
||||
}
|
||||
@@ -610,7 +609,7 @@ public sealed class MarkingSet
|
||||
public ReverseMarkingEnumerator GetReverseEnumerator()
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
foreach (var (_, list) in _markings)
|
||||
foreach (var (_, list) in Markings)
|
||||
{
|
||||
markings.AddRange(list);
|
||||
}
|
||||
@@ -626,7 +625,7 @@ public sealed class MarkingSet
|
||||
public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category)
|
||||
{
|
||||
var markings = new List<Marking>();
|
||||
if (_markings.TryGetValue(category, out var listing))
|
||||
if (Markings.TryGetValue(category, out var listing))
|
||||
{
|
||||
markings = new(listing);
|
||||
}
|
||||
@@ -636,8 +635,8 @@ public sealed class MarkingSet
|
||||
|
||||
public bool CategoryEquals(MarkingCategories category, MarkingSet other)
|
||||
{
|
||||
if (!_markings.TryGetValue(category, out var markings)
|
||||
|| !other._markings.TryGetValue(category, out var markingsOther))
|
||||
if (!Markings.TryGetValue(category, out var markings)
|
||||
|| !other.Markings.TryGetValue(category, out var markingsOther))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -647,7 +646,7 @@ public sealed class MarkingSet
|
||||
|
||||
public bool Equals(MarkingSet other)
|
||||
{
|
||||
foreach (var (category, _) in _markings)
|
||||
foreach (var (category, _) in Markings)
|
||||
{
|
||||
if (!CategoryEquals(category, other))
|
||||
{
|
||||
@@ -665,7 +664,7 @@ public sealed class MarkingSet
|
||||
/// <returns>Enumerator of marking categories that were different between the two.</returns>
|
||||
public IEnumerable<MarkingCategories> CategoryDifference(MarkingSet other)
|
||||
{
|
||||
foreach (var (category, _) in _markings)
|
||||
foreach (var (category, _) in Markings)
|
||||
{
|
||||
if (!CategoryEquals(category, other))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Humanoid.Prototypes;
|
||||
|
||||
|
||||
200
Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
Normal file
200
Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
/// <summary>
|
||||
/// HumanoidSystem. Primarily deals with the appearance and visual data
|
||||
/// of a humanoid entity. HumanoidVisualizer is what deals with actually
|
||||
/// organizing the sprites and setting up the sprite component's layers.
|
||||
///
|
||||
/// This is a shared system, because while it is server authoritative,
|
||||
/// you still need a local copy so that players can set up their
|
||||
/// characters.
|
||||
/// </summary>
|
||||
public abstract class SharedHumanoidAppearanceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
public const string DefaultSpecies = "Human";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentGetState>(OnGetState);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new HumanoidAppearanceState(component.MarkingSet,
|
||||
component.PermanentlyHidden,
|
||||
component.HiddenLayers,
|
||||
component.CustomBaseLayers,
|
||||
component.Sex,
|
||||
component.Gender,
|
||||
component.Age,
|
||||
component.Species,
|
||||
component.SkinColor,
|
||||
component.EyeColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles a humanoid's sprite layer visibility.
|
||||
/// </summary>
|
||||
/// <param name="uid">Humanoid mob's UID</param>
|
||||
/// <param name="layer">Layer to toggle visibility for</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetLayerVisibility(EntityUid uid,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent = false,
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
var dirty = false;
|
||||
SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
if (dirty)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the visibility for multiple layers at once on a humanoid's sprite.
|
||||
/// </summary>
|
||||
/// <param name="uid">Humanoid mob's UID</param>
|
||||
/// <param name="layers">An enumerable of all sprite layers that are going to have their visibility set</param>
|
||||
/// <param name="visible">The visibility state of the layers given</param>
|
||||
/// <param name="permanent">If this is a permanent change, or temporary. Permanent layers are stored in their own hash set.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetLayersVisibility(EntityUid uid, IEnumerable<HumanoidVisualLayers> layers, bool visible, bool permanent = false,
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
var dirty = false;
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
protected virtual void SetLayerVisibility(
|
||||
EntityUid uid,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent,
|
||||
ref bool dirty)
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
if (permanent)
|
||||
dirty |= humanoid.PermanentlyHidden.Remove(layer);
|
||||
|
||||
dirty |= humanoid.HiddenLayers.Remove(layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (permanent)
|
||||
dirty |= humanoid.PermanentlyHidden.Add(layer);
|
||||
|
||||
dirty |= humanoid.HiddenLayers.Add(layer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a humanoid mob's species. This will change their base sprites, as well as their current
|
||||
/// set of markings to fit against the mob's new species.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="species">The species to set the mob to. Will return if the species prototype was invalid.</param>
|
||||
/// <param name="sync">Whether to immediately synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetSpecies(EntityUid uid, string species, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || !_prototypeManager.TryIndex<SpeciesPrototype>(species, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.Species = species;
|
||||
humanoid.MarkingSet.FilterSpecies(species, _markingManager);
|
||||
var oldMarkings = humanoid.MarkingSet.GetForwardEnumerator().ToList();
|
||||
humanoid.MarkingSet = new(oldMarkings, prototype.MarkingPoints, _markingManager, _prototypeManager);
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the skin color of this humanoid mob. Will only affect base layers that are not custom,
|
||||
/// custom base layers should use <see cref="SetBaseLayerColor"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="skinColor">Skin color to set on the humanoid mob.</param>
|
||||
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public virtual void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the base layer ID of this humanoid mob. A humanoid mob's 'base layer' is
|
||||
/// the skin sprite that is applied to the mob's sprite upon appearance refresh.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="layer">The layer to target on this humanoid mob.</param>
|
||||
/// <param name="id">The ID of the sprite to use. See <see cref="HumanoidSpeciesSpriteLayer"/>.</param>
|
||||
/// <param name="sync">Whether to synchronize this to the humanoid mob, or not.</param>
|
||||
/// <param name="humanoid">Humanoid component of the entity</param>
|
||||
public void SetBaseLayerId(EntityUid uid, HumanoidVisualLayers layer, string id, bool sync = true,
|
||||
HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
if (humanoid.CustomBaseLayers.TryGetValue(layer, out var info))
|
||||
humanoid.CustomBaseLayers[layer] = info with { ID = id };
|
||||
else
|
||||
humanoid.CustomBaseLayers[layer] = new(id);
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of this humanoid mob's base layer. See <see cref="SetBaseLayerId"/> for a
|
||||
/// description of how base layers work.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid mob's UID.</param>
|
||||
/// <param name="layer">The layer to target on this humanoid mob.</param>
|
||||
/// <param name="color">The color to set this base layer to.</param>
|
||||
public void SetBaseLayerColor(EntityUid uid, HumanoidVisualLayers layer, Color? color, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
return;
|
||||
|
||||
humanoid.CustomBaseLayers[layer] = humanoid.CustomBaseLayers[layer] with { Color = color };
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Robust.Shared.Serialization;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
@@ -40,6 +41,7 @@ public sealed class HumanoidMarkingModifierBaseLayersSetMessage : BoundUserInter
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HumanoidMarkingModifierState : BoundUserInterfaceState
|
||||
{
|
||||
// TODO just use the component state, remove the BUI state altogether.
|
||||
public HumanoidMarkingModifierState(MarkingSet markingSet, string species, Color skinColor, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers)
|
||||
{
|
||||
MarkingSet = markingSet;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
/// <summary>
|
||||
/// HumanoidSystem. Primarily deals with the appearance and visual data
|
||||
/// of a humanoid entity. HumanoidVisualizer is what deals with actually
|
||||
/// organizing the sprites and setting up the sprite component's layers.
|
||||
///
|
||||
/// This is a shared system, because while it is server authoritative,
|
||||
/// you still need a local copy so that players can set up their
|
||||
/// characters.
|
||||
/// </summary>
|
||||
public abstract class SharedHumanoidSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public const string DefaultSpecies = "Human";
|
||||
|
||||
public void SetAppearance(EntityUid uid,
|
||||
string species,
|
||||
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayer,
|
||||
Color skinColor,
|
||||
Sex sex,
|
||||
List<HumanoidVisualLayers> visLayers,
|
||||
List<Marking> markings)
|
||||
{
|
||||
var data = new HumanoidVisualizerData(species, customBaseLayer, skinColor, sex, visLayers, markings);
|
||||
|
||||
// This should raise a HumanoidAppearanceUpdateEvent, but that requires this component to be made networked and
|
||||
// I cbf doing that atm.
|
||||
_appearance.SetData(uid, HumanoidVisualizerKey.Key, data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user