Makes humanoid appearance component networked. (#13009)

Fixes https://github.com/space-wizards/space-station-14/issues/12248
This commit is contained in:
Leon Friedrich
2023-01-24 13:38:19 +13:00
committed by GitHub
parent 7ce8f7634a
commit 48bcd30ef9
50 changed files with 878 additions and 1074 deletions

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

View File

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

View File

@@ -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.

View File

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

View File

@@ -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>

View File

@@ -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))
{

View File

@@ -1,5 +1,6 @@
using Content.Shared.Preferences;
using Robust.Shared.Prototypes;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Shared.Humanoid.Prototypes;

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

View File

@@ -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;

View File

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