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

@@ -65,9 +65,8 @@ public sealed class ClientClothingSystem : ClothingSystem
if (!TryComp(uid, out SpriteComponent? sprite) || !sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer)) if (!TryComp(uid, out SpriteComponent? sprite) || !sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
return; return;
if (!args.AppearanceData.TryGetValue(HumanoidVisualizerKey.Key, out object? obj) if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid)
|| obj is not HumanoidVisualizerData data || humanoid.Sex != Sex.Female
|| data.Sex != Sex.Female
|| !_inventorySystem.TryGetSlotEntity(uid, "jumpsuit", out var suit, component) || !_inventorySystem.TryGetSlotEntity(uid, "jumpsuit", out var suit, component)
|| !TryComp(suit, out ClothingComponent? clothing)) || !TryComp(suit, out ClothingComponent? clothing))
{ {
@@ -219,8 +218,7 @@ public sealed class ClientClothingSystem : ClothingSystem
if (slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var suitLayer)) if (slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var suitLayer))
{ {
if (_appearance.TryGetData<HumanoidVisualizerData>(equipee, HumanoidVisualizerKey.Key, out var data) if (TryComp(equipee, out HumanoidAppearanceComponent? humanoid) && humanoid.Sex == Sex.Female)
&& data.Sex == Sex.Female)
{ {
sprite.LayerSetState(suitLayer, clothingComponent.FemaleMask switch sprite.LayerSetState(suitLayer, clothingComponent.FemaleMask switch
{ {

View File

@@ -0,0 +1,351 @@
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Client.Humanoid;
public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly MarkingManager _markingManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentHandleState>(OnHandleState);
}
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentHandleState args)
{
if (args.Current is not HumanoidAppearanceState state)
return;
ApplyState(uid, component, Comp<SpriteComponent>(uid), state);
}
private void ApplyState(EntityUid uid, HumanoidAppearanceComponent component, SpriteComponent sprite, HumanoidAppearanceState state)
{
component.Sex = state.Sex;
component.Species = state.Species;
component.Age = state.Age;
component.SkinColor = state.SkinColor;
component.EyeColor = state.EyeColor;
component.HiddenLayers = new(state.HiddenLayers);
component.PermanentlyHidden = new(state.PermanentlyHidden);
component.CustomBaseLayers = state.CustomBaseLayers.ShallowClone();
UpdateLayers(component, sprite);
ApplyMarkingSet(uid, state.Markings, component, sprite);
sprite[sprite.LayerMapReserveBlank(HumanoidVisualLayers.Eyes)].Color = state.EyeColor;
}
private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
=> humanoid.HiddenLayers.Contains(layer) || humanoid.PermanentlyHidden.Contains(layer);
private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
{
var oldLayers = new HashSet<HumanoidVisualLayers>(component.BaseLayers.Keys);
component.BaseLayers.Clear();
// add default species layers
var speciesProto = _prototypeManager.Index<SpeciesPrototype>(component.Species);
var baseSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>(speciesProto.SpriteSet);
foreach (var (key, id) in baseSprites.Sprites)
{
oldLayers.Remove(key);
if (!component.CustomBaseLayers.ContainsKey(key))
SetLayerData(component, sprite, key, id, sexMorph: true);
}
// add custom layers
foreach (var (key, info) in component.CustomBaseLayers)
{
oldLayers.Remove(key);
SetLayerData(component, sprite, key, info.ID, sexMorph: false, color: info.Color); ;
}
// hide old layers
// TODO maybe just remove them altogether?
foreach (var key in oldLayers)
{
if (sprite.LayerMapTryGet(key, out var index))
sprite[index].Visible = false;
}
}
private void SetLayerData(
HumanoidAppearanceComponent component,
SpriteComponent sprite,
HumanoidVisualLayers key,
string protoId,
bool sexMorph = false,
Color? color = null)
{
if (sexMorph)
protoId = HumanoidVisualLayersExtension.GetSexMorph(key, component.Sex, protoId);
var proto = _prototypeManager.Index<HumanoidSpeciesSpriteLayer>(protoId);
component.BaseLayers[key] = proto;
var layerIndex = sprite.LayerMapReserveBlank(key);
var layer = sprite[layerIndex];
if (color != null)
layer.Color = color.Value;
else if (proto.MatchSkin)
layer.Color = proto.MatchSkin ? component.SkinColor.WithAlpha(proto.LayerAlpha) : Color.White;
if (proto.BaseSprite != null)
sprite.LayerSetSprite(layerIndex, proto.BaseSprite);
layer.Visible = !IsHidden(component, key);
}
/// <summary>
/// Loads a profile directly into a humanoid.
/// </summary>
/// <param name="uid">The humanoid entity's UID</param>
/// <param name="profile">The profile to load.</param>
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
/// <remarks>
/// This should not be used if the entity is owned by the server. The server will otherwise
/// override this with the appearance data it sends over.
/// </remarks>
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid))
{
return;
}
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>();
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
_prototypeManager);
markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
// legacy: remove in the future?
markings.RemoveCategory(MarkingCategories.Hair);
markings.RemoveCategory(MarkingCategories.FacialHair);
var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
markings.AddBack(MarkingCategories.Hair, hair);
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
new[] { profile.Appearance.FacialHairColor });
markings.AddBack(MarkingCategories.FacialHair, facialHair);
markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
DebugTools.Assert(uid.IsClientSide());
var state = new HumanoidAppearanceState(markings,
new(),
new(),
customBaseLayers,
profile.Sex,
profile.Gender,
profile.Age,
profile.Species,
profile.Appearance.SkinColor,
profile.Appearance.EyeColor);
ApplyState(uid, humanoid, Comp<SpriteComponent>(uid), state);
}
private void ApplyMarkingSet(EntityUid uid,
MarkingSet newMarkings,
HumanoidAppearanceComponent humanoid,
SpriteComponent sprite)
{
// skip this entire thing if both sets are empty
if (humanoid.MarkingSet.Markings.Count == 0 && newMarkings.Markings.Count == 0)
return;
// I am lazy and I CBF resolving the previous mess, so I'm just going to nuke the markings.
// Really, markings should probably be a separate component altogether.
ClearAllMarkings(uid, humanoid, sprite);
humanoid.MarkingSet = new(newMarkings);
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
{
foreach (var marking in markingList)
{
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
}
}
}
private void ClearAllMarkings(EntityUid uid, HumanoidAppearanceComponent humanoid,
SpriteComponent sprite)
{
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
{
foreach (var marking in markingList)
{
RemoveMarking(uid, marking, sprite);
}
}
}
private void ClearMarkings(EntityUid uid, List<Marking> markings, HumanoidAppearanceComponent humanoid,
SpriteComponent spriteComp)
{
foreach (var marking in markings)
{
RemoveMarking(uid, marking, spriteComp);
}
}
private void RemoveMarking(EntityUid uid, Marking marking,
SpriteComponent spriteComp)
{
if (!_markingManager.TryGetMarking(marking, out var prototype))
{
return;
}
foreach (var sprite in prototype.Sprites)
{
if (sprite is not SpriteSpecifier.Rsi rsi)
{
continue;
}
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
if (!spriteComp.LayerMapTryGet(layerId, out var index))
{
continue;
}
spriteComp.LayerMapRemove(layerId);
spriteComp.RemoveLayer(index);
}
}
private void ApplyMarking(EntityUid uid,
MarkingPrototype markingPrototype,
IReadOnlyList<Color>? colors,
bool visible,
HumanoidAppearanceComponent humanoid,
SpriteComponent sprite)
{
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
{
return;
}
visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
&& setting.AllowsMarkings;
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
{
if (markingPrototype.Sprites[j] is not SpriteSpecifier.Rsi rsi)
{
continue;
}
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
if (!sprite.LayerMapTryGet(layerId, out _))
{
var layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
sprite.LayerMapSet(layerId, layer);
sprite.LayerSetSprite(layerId, rsi);
}
sprite.LayerSetVisible(layerId, visible);
if (!visible || setting == null) // this is kinda implied
{
continue;
}
if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
{
var skinColor = humanoid.SkinColor;
skinColor.A = setting.LayerAlpha;
sprite.LayerSetColor(layerId, skinColor);
}
else
{
sprite.LayerSetColor(layerId, colors[j]);
}
}
}
public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid) || humanoid.SkinColor == skinColor)
return;
humanoid.SkinColor = skinColor;
if (sync)
Dirty(humanoid);
if (!TryComp(uid, out SpriteComponent? sprite))
return;
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
{
if (!spriteInfo.MatchSkin)
continue;
var index = sprite.LayerMapReserveBlank(layer);
sprite[index].Color = skinColor.WithAlpha(spriteInfo.LayerAlpha);
}
}
protected override void SetLayerVisibility(
EntityUid uid,
HumanoidAppearanceComponent humanoid,
HumanoidVisualLayers layer,
bool visible,
bool permanent,
ref bool dirty)
{
base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
var sprite = Comp<SpriteComponent>(uid);
if (!sprite.LayerMapTryGet(layer, out var index))
{
if (!visible)
return;
else
index = sprite.LayerMapReserveBlank(layer);
}
var spriteLayer = sprite[index];
if (spriteLayer.Visible == visible)
return;
spriteLayer.Visible = visible;
// I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
{
foreach (var marking in markingList)
{
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
}
}
}
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Markings;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Client.Humanoid; namespace Content.Client.Humanoid;

View File

@@ -4,6 +4,7 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Client.Humanoid; namespace Content.Client.Humanoid;
@@ -63,7 +64,7 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow
continue; continue;
} }
modifier.SetState(true, layerInfo.ID, layerInfo.Color); modifier.SetState(true, layerInfo.ID, layerInfo.Color ?? Color.White);
} }
} }

View File

@@ -1,64 +0,0 @@
using System.Linq;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Robust.Shared.Prototypes;
namespace Content.Client.Humanoid;
public sealed class HumanoidSystem : SharedHumanoidSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly MarkingManager _markingManager = default!;
/// <summary>
/// Loads a profile directly into a humanoid.
/// </summary>
/// <param name="uid">The humanoid entity's UID</param>
/// <param name="profile">The profile to load.</param>
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
/// <remarks>
/// This should not be used if the entity is owned by the server. The server will otherwise
/// override this with the appearance data it sends over.
/// </remarks>
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid))
{
return;
}
humanoid.Species = profile.Species;
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>
{
[HumanoidVisualLayers.Eyes] = new CustomBaseLayerInfo(string.Empty, profile.Appearance.EyeColor)
};
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
_prototypeManager);
markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
// legacy: remove in the future?
markings.RemoveCategory(MarkingCategories.Hair);
markings.RemoveCategory(MarkingCategories.FacialHair);
var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
markings.AddBack(MarkingCategories.Hair, hair);
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
new[] { profile.Appearance.FacialHairColor });
markings.AddBack(MarkingCategories.FacialHair, facialHair);
markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
SetAppearance(uid,
profile.Species,
customBaseLayers,
profile.Appearance.SkinColor,
profile.Sex,
new(), // doesn't exist yet
markings.GetForwardEnumerator().ToList());
}
}

View File

@@ -1,447 +0,0 @@
using System.Linq;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Humanoid;
public sealed class HumanoidVisualizerSystem : VisualizerSystem<HumanoidComponent>
{
[Dependency] private IPrototypeManager _prototypeManager = default!;
[Dependency] private MarkingManager _markingManager = default!;
protected override void OnAppearanceChange(EntityUid uid, HumanoidComponent component, ref AppearanceChangeEvent args)
{
base.OnAppearanceChange(uid, component, ref args);
if (args.Sprite == null)
{
return;
}
if (!args.AppearanceData.TryGetValue(HumanoidVisualizerKey.Key, out var dataRaw)
|| dataRaw is not HumanoidVisualizerData data)
{
return;
}
if (!_prototypeManager.TryIndex(data.Species, out SpeciesPrototype? speciesProto)
|| !_prototypeManager.TryIndex(speciesProto.SpriteSet, out HumanoidSpeciesBaseSpritesPrototype? baseSprites))
{
return;
}
var dirty = data.SkinColor != component.SkinColor || data.Sex != component.Sex;
component.Sex = data.Sex;
if (data.CustomBaseLayerInfo.Count != 0)
{
dirty |= MergeCustomBaseSprites(uid, baseSprites.Sprites, data.CustomBaseLayerInfo, component);
}
else
{
dirty |= MergeCustomBaseSprites(uid, baseSprites.Sprites, null, component);
}
if (dirty)
{
ApplyBaseSprites(uid, component, args.Sprite);
ApplySkinColor(uid, data.SkinColor, component, args.Sprite);
}
if (data.CustomBaseLayerInfo.Count != 0)
{
foreach (var (layer, info) in data.CustomBaseLayerInfo)
{
SetBaseLayerColor(uid, layer, info.Color, args.Sprite);
}
}
var layerVis = data.LayerVisibility.ToHashSet();
dirty |= ReplaceHiddenLayers(uid, layerVis, component, args.Sprite);
DiffAndApplyMarkings(uid, data.Markings, dirty, component, args.Sprite);
}
private bool ReplaceHiddenLayers(EntityUid uid, HashSet<HumanoidVisualLayers> hiddenLayers,
HumanoidComponent humanoid, SpriteComponent sprite)
{
if (hiddenLayers.SetEquals(humanoid.HiddenLayers))
{
return false;
}
SetSpriteVisibility(uid, hiddenLayers, false, sprite);
humanoid.HiddenLayers.ExceptWith(hiddenLayers);
SetSpriteVisibility(uid, humanoid.HiddenLayers, true, sprite);
humanoid.HiddenLayers.Clear();
humanoid.HiddenLayers.UnionWith(hiddenLayers);
return true;
}
private void SetSpriteVisibility(EntityUid uid, HashSet<HumanoidVisualLayers> layers, bool visibility, SpriteComponent sprite)
{
foreach (var layer in layers)
{
if (!sprite.LayerMapTryGet(layer, out var index))
{
continue;
}
sprite[index].Visible = visibility;
}
}
private void DiffAndApplyMarkings(EntityUid uid,
List<Marking> newMarkings,
bool layersDirty,
HumanoidComponent humanoid,
SpriteComponent sprite)
{
// skip this entire thing if both sets are empty
if (humanoid.CurrentClientMarkings.Count == 0 && newMarkings.Count == 0)
{
return;
}
var dirtyMarkings = new List<int>();
var dirtyRangeStart = humanoid.CurrentClientMarkings.Count == 0 ? 0 : -1;
// edge cases:
// humanoid.CurrentClientMarkings < newMarkings.Count
// - check if count matches this condition before diffing
// - if count is unequal, set dirty range to start from humanoid.CurrentClientMarkings.Count
// humanoid.CurrentClientMarkings > newMarkings.Count, no dirty markings
// - break count upon meeting this condition
// - clear markings from newMarkings.Count to humanoid.CurrentClientMarkings.Count - newMarkings.Count
for (var i = 0; i < humanoid.CurrentClientMarkings.Count; i++)
{
// if we've reached the end of the new set of markings,
// then that means it's time to finish
if (newMarkings.Count == i)
{
break;
}
// if the marking is different here, set the range start to i and break, we need
// to rebuild all markings starting from i
if (humanoid.CurrentClientMarkings[i].MarkingId != newMarkings[i].MarkingId)
{
dirtyRangeStart = i;
break;
}
// otherwise, we add the current marking to dirtyMarkings if it has different
// settings
// however: if the hidden layers are set to dirty, then we need to
// instead just add every single marking, since we don't know ahead of time
// where these markings go
if (humanoid.CurrentClientMarkings[i] != newMarkings[i] || layersDirty)
{
dirtyMarkings.Add(i);
}
}
foreach (var i in dirtyMarkings)
{
if (!_markingManager.TryGetMarking(newMarkings[i], out var dirtyMarking))
{
continue;
}
ApplyMarking(uid, dirtyMarking, newMarkings[i].MarkingColors, newMarkings[i].Visible, humanoid, sprite);
}
if (humanoid.CurrentClientMarkings.Count < newMarkings.Count && dirtyRangeStart < 0)
{
dirtyRangeStart = humanoid.CurrentClientMarkings.Count;
}
if (dirtyRangeStart >= 0)
{
var range = newMarkings.GetRange(dirtyRangeStart, newMarkings.Count - dirtyRangeStart);
if (humanoid.CurrentClientMarkings.Count > 0)
{
var oldRange = humanoid.CurrentClientMarkings.GetRange(dirtyRangeStart, humanoid.CurrentClientMarkings.Count - dirtyRangeStart);
ClearMarkings(uid, oldRange, humanoid, sprite);
}
ApplyMarkings(uid, range, humanoid, sprite);
}
else if (humanoid.CurrentClientMarkings.Count != newMarkings.Count)
{
if (newMarkings.Count == 0)
{
ClearAllMarkings(uid, humanoid, sprite);
}
else if (humanoid.CurrentClientMarkings.Count > newMarkings.Count)
{
var rangeStart = newMarkings.Count;
var rangeCount = humanoid.CurrentClientMarkings.Count - newMarkings.Count;
var range = humanoid.CurrentClientMarkings.GetRange(rangeStart, rangeCount);
ClearMarkings(uid, range, humanoid, sprite);
}
}
if (dirtyMarkings.Count > 0 || dirtyRangeStart >= 0 || humanoid.CurrentClientMarkings.Count != newMarkings.Count)
{
humanoid.CurrentClientMarkings = newMarkings;
}
}
private void ClearAllMarkings(EntityUid uid, HumanoidComponent humanoid,
SpriteComponent spriteComp)
{
ClearMarkings(uid, humanoid.CurrentClientMarkings, humanoid, spriteComp);
}
private void ClearMarkings(EntityUid uid, List<Marking> markings, HumanoidComponent humanoid,
SpriteComponent spriteComp)
{
foreach (var marking in markings)
{
RemoveMarking(uid, marking, spriteComp);
}
}
private void RemoveMarking(EntityUid uid, Marking marking,
SpriteComponent spriteComp)
{
if (!_markingManager.TryGetMarking(marking, out var prototype))
{
return;
}
foreach (var sprite in prototype.Sprites)
{
if (sprite is not SpriteSpecifier.Rsi rsi)
{
continue;
}
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
if (!spriteComp.LayerMapTryGet(layerId, out var index))
{
continue;
}
spriteComp.LayerMapRemove(layerId);
spriteComp.RemoveLayer(index);
}
}
private void ApplyMarkings(EntityUid uid,
List<Marking> markings,
HumanoidComponent humanoid,
SpriteComponent spriteComp)
{
foreach (var marking in new ReverseMarkingEnumerator(markings))
{
if (!_markingManager.TryGetMarking(marking, out var markingPrototype))
{
continue;
}
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, spriteComp);
}
}
private void ApplyMarking(EntityUid uid,
MarkingPrototype markingPrototype,
IReadOnlyList<Color>? colors,
bool visible,
HumanoidComponent humanoid,
SpriteComponent sprite)
{
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
{
return;
}
visible &= !humanoid.HiddenLayers.Contains(markingPrototype.BodyPart);
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
&& setting.AllowsMarkings;
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
{
if (markingPrototype.Sprites[j] is not SpriteSpecifier.Rsi rsi)
{
continue;
}
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
if (!sprite.LayerMapTryGet(layerId, out _))
{
var layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
sprite.LayerMapSet(layerId, layer);
sprite.LayerSetSprite(layerId, rsi);
}
sprite.LayerSetVisible(layerId, visible);
if (!visible || setting == null) // this is kinda implied
{
continue;
}
if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
{
var skinColor = humanoid.SkinColor;
skinColor.A = setting.LayerAlpha;
sprite.LayerSetColor(layerId, skinColor);
}
else
{
sprite.LayerSetColor(layerId, colors[j]);
}
}
}
private void ApplySkinColor(EntityUid uid,
Color skinColor,
HumanoidComponent humanoid,
SpriteComponent spriteComp)
{
humanoid.SkinColor = skinColor;
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
{
if (!spriteInfo.MatchSkin)
{
continue;
}
var color = skinColor;
color.A = spriteInfo.LayerAlpha;
SetBaseLayerColor(uid, layer, color, spriteComp);
}
}
private void SetBaseLayerColor(EntityUid uid, HumanoidVisualLayers layer, Color color,
SpriteComponent sprite)
{
if (!sprite.LayerMapTryGet(layer, out var index))
{
return;
}
sprite[index].Color = color;
}
private bool MergeCustomBaseSprites(EntityUid uid, Dictionary<HumanoidVisualLayers, string> baseSprites,
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>? customBaseSprites,
HumanoidComponent humanoid)
{
var newBaseLayers = new Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer>();
foreach (var (key, id) in baseSprites)
{
var sexMorph = humanoid.Sex switch
{
Sex.Male when HumanoidVisualLayersExtension.HasSexMorph(key) => $"{id}Male",
Sex.Female when HumanoidVisualLayersExtension.HasSexMorph(key) => $"{id}Female",
_ => id
};
if (!_prototypeManager.TryIndex(sexMorph, out HumanoidSpeciesSpriteLayer? baseLayer))
{
continue;
}
if (!newBaseLayers.TryAdd(key, baseLayer))
{
newBaseLayers[key] = baseLayer;
}
}
if (customBaseSprites == null)
{
return IsDirty(newBaseLayers);
}
foreach (var (key, info) in customBaseSprites)
{
if (!_prototypeManager.TryIndex(info.ID, out HumanoidSpeciesSpriteLayer? baseLayer))
{
continue;
}
if (!newBaseLayers.TryAdd(key, baseLayer))
{
newBaseLayers[key] = baseLayer;
}
}
bool IsDirty(Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> newBaseLayers)
{
var dirty = false;
if (humanoid.BaseLayers.Count != newBaseLayers.Count)
{
dirty = true;
humanoid.BaseLayers = newBaseLayers;
return dirty;
}
foreach (var (key, info) in humanoid.BaseLayers)
{
if (!newBaseLayers.TryGetValue(key, out var newInfo))
{
dirty = true;
break;
}
if (info.ID != newInfo.ID)
{
dirty = true;
break;
}
}
if (dirty)
{
humanoid.BaseLayers = newBaseLayers;
}
return dirty;
}
return IsDirty(newBaseLayers);
}
private void ApplyBaseSprites(EntityUid uid,
HumanoidComponent humanoid,
SpriteComponent spriteComp)
{
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
{
if (spriteInfo.BaseSprite != null && spriteComp.LayerMapTryGet(layer, out var index))
{
switch (spriteInfo.BaseSprite)
{
case SpriteSpecifier.Rsi rsi:
spriteComp.LayerSetRSI(index, rsi.RsiPath);
spriteComp.LayerSetState(index, rsi.RsiState);
break;
case SpriteSpecifier.Texture texture:
spriteComp.LayerSetTexture(index, texture.TexturePath);
break;
}
}
}
}
}

View File

@@ -34,7 +34,7 @@ public sealed partial class MarkingPicker : Control
private List<MarkingCategories> _markingCategories = Enum.GetValues<MarkingCategories>().ToList(); private List<MarkingCategories> _markingCategories = Enum.GetValues<MarkingCategories>().ToList();
private string _currentSpecies = SharedHumanoidSystem.DefaultSpecies; private string _currentSpecies = SharedHumanoidAppearanceSystem.DefaultSpecies;
public Color CurrentSkinColor = Color.White; public Color CurrentSkinColor = Color.White;
private readonly HashSet<MarkingCategories> _ignoreCategories = new(); private readonly HashSet<MarkingCategories> _ignoreCategories = new();
@@ -361,12 +361,13 @@ public sealed partial class MarkingPicker : Control
colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" }); colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" });
colorContainer.AddChild(colorSelector); colorContainer.AddChild(colorSelector);
var listing = _currentMarkings[_selectedMarkingCategory]; var listing = _currentMarkings.Markings[_selectedMarkingCategory];
var color = listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i];
var currentColor = new Color( var currentColor = new Color(
listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].RByte, color.RByte,
listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].GByte, color.GByte,
listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].BByte color.BByte
); );
colorSelector.Color = currentColor; colorSelector.Color = currentColor;
_currentMarkingColors.Add(currentColor); _currentMarkingColors.Add(currentColor);
@@ -394,7 +395,7 @@ public sealed partial class MarkingPicker : Control
_selectedMarking.IconModulate = _currentMarkingColors[colorIndex]; _selectedMarking.IconModulate = _currentMarkingColors[colorIndex];
var marking = new Marking(_currentMarkings[_selectedMarkingCategory][markingIndex]); var marking = new Marking(_currentMarkings.Markings[_selectedMarkingCategory][markingIndex]);
marking.SetColor(colorIndex, _currentMarkingColors[colorIndex]); marking.SetColor(colorIndex, _currentMarkingColors[colorIndex]);
_currentMarkings.Replace(_selectedMarkingCategory, markingIndex, marking); _currentMarkings.Replace(_selectedMarkingCategory, markingIndex, marking);

View File

@@ -128,7 +128,7 @@ namespace Content.Client.Lobby.UI
_viewBox.AddChild(viewWest); _viewBox.AddChild(viewWest);
_viewBox.AddChild(viewEast); _viewBox.AddChild(viewEast);
_summaryLabel.Text = selectedCharacter.Summary; _summaryLabel.Text = selectedCharacter.Summary;
EntitySystem.Get<HumanoidSystem>().LoadProfile(_previewDummy.Value, selectedCharacter); EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
GiveDummyJobClothes(_previewDummy.Value, selectedCharacter); GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
} }
} }

View File

@@ -168,10 +168,10 @@ namespace Content.Client.Preferences.UI
} }
else else
{ {
_previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(SharedHumanoidSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); _previewDummy = entityManager.SpawnEntity(prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace);
} }
EntitySystem.Get<HumanoidSystem>().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile); EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile);
if (humanoid != null) if (humanoid != null)
{ {

View File

@@ -553,7 +553,7 @@ namespace Content.Client.Preferences.UI
#endregion FlavorText #endregion FlavorText
#region Dummy #region Dummy
var species = Profile?.Species ?? SharedHumanoidSystem.DefaultSpecies; var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype; var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
if (_previewDummy != null) if (_previewDummy != null)
@@ -693,7 +693,7 @@ namespace Content.Client.Preferences.UI
private void RebuildSpriteView() private void RebuildSpriteView()
{ {
var species = Profile?.Species ?? SharedHumanoidSystem.DefaultSpecies; var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype; var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
if (_previewDummy != null) if (_previewDummy != null)
@@ -1032,7 +1032,7 @@ namespace Content.Client.Preferences.UI
if (Profile is null) if (Profile is null)
return; return;
EntitySystem.Get<HumanoidSystem>().LoadProfile(_previewDummy!.Value, Profile); EntitySystem.Get<HumanoidAppearanceSystem>().LoadProfile(_previewDummy!.Value, Profile);
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile); LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
} }

View File

@@ -25,7 +25,7 @@ public sealed class BodySystem : SharedBodySystem
{ {
[Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -83,7 +83,7 @@ public sealed class BodySystem : SharedBodySystem
return false; return false;
if (part.Body is { } body && if (part.Body is { } body &&
TryComp<HumanoidComponent>(body, out var humanoid)) TryComp<HumanoidAppearanceComponent>(body, out var humanoid))
{ {
var layer = part.ToHumanoidLayers(); var layer = part.ToHumanoidLayers();
if (layer != null) if (layer != null)
@@ -103,7 +103,7 @@ public sealed class BodySystem : SharedBodySystem
if (!base.DropPart(partId, part)) if (!base.DropPart(partId, part))
return false; return false;
if (oldBody == null || !TryComp<HumanoidComponent>(oldBody, out var humanoid)) if (oldBody == null || !TryComp<HumanoidAppearanceComponent>(oldBody, out var humanoid))
return true; return true;
var layer = part.ToHumanoidLayers(); var layer = part.ToHumanoidLayers();

View File

@@ -31,6 +31,7 @@ using Robust.Shared.Random;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Content.Shared.Humanoid;
namespace Content.Server.Cloning namespace Content.Server.Cloning
{ {
@@ -41,7 +42,7 @@ namespace Content.Server.Cloning
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly EuiManager _euiManager = null!; [Dependency] private readonly EuiManager _euiManager = null!;
[Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!; [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!;
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!; [Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
@@ -170,7 +171,7 @@ namespace Content.Server.Cloning
if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client))
return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad.
if (!TryComp<HumanoidComponent>(bodyToClone, out var humanoid)) if (!TryComp<HumanoidAppearanceComponent>(bodyToClone, out var humanoid))
return false; // whatever body was to be cloned, was not a humanoid return false; // whatever body was to be cloned, was not a humanoid
if (!_prototype.TryIndex<SpeciesPrototype>(humanoid.Species, out var speciesPrototype)) if (!_prototype.TryIndex<SpeciesPrototype>(humanoid.Species, out var speciesPrototype))

View File

@@ -1,39 +1,7 @@
using Content.Server.Humanoid;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems; using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Humanoid;
using Content.Shared.Inventory.Events;
using Content.Shared.Tag;
namespace Content.Server.Clothing; namespace Content.Server.Clothing;
public sealed class ServerClothingSystem : ClothingSystem public sealed class ServerClothingSystem : ClothingSystem
{ {
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
protected override void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args)
{
base.OnGotEquipped(uid, component, args);
// why the fuck is humanoid visuals server-only???
if (args.Slot == "head"
&& _tagSystem.HasTag(args.Equipment, "HidesHair"))
{
_humanoidSystem.ToggleHiddenLayer(args.Equipee, HumanoidVisualLayers.Hair);
}
}
protected override void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
{
base.OnGotUnequipped(uid, component, args);
// why the fuck is humanoid visuals server-only???
if (args.Slot == "head"
&& _tagSystem.HasTag(args.Equipment, "HidesHair"))
{
_humanoidSystem.ToggleHiddenLayer(args.Equipee, HumanoidVisualLayers.Hair);
}
}
} }

View File

@@ -303,7 +303,7 @@ namespace Content.Server.Dragon
var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate); var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate);
//Humanoid devours allow dragon to get eggs, corpses included //Humanoid devours allow dragon to get eggs, corpses included
if (!EntityManager.HasComponent<HumanoidComponent>(args.Target)) if (!EntityManager.HasComponent<HumanoidAppearanceComponent>(args.Target))
{ {
ichorInjection.ScaleSolution(0.5f); ichorInjection.ScaleSolution(0.5f);
} }

View File

@@ -148,7 +148,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
private void CheckRoundEnd(EntityUid target) private void CheckRoundEnd(EntityUid target)
{ {
//we only care about players, not monkeys and such. //we only care about players, not monkeys and such.
if (!HasComp<HumanoidComponent>(target)) if (!HasComp<HumanoidAppearanceComponent>(target))
return; return;
var percent = GetInfectedPercentage(out var num); var percent = GetInfectedPercentage(out var num);
@@ -196,7 +196,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem
private float GetInfectedPercentage(out List<EntityUid> livingHumans) private float GetInfectedPercentage(out List<EntityUid> livingHumans)
{ {
var allPlayers = EntityQuery<HumanoidComponent, MobStateComponent>(true); var allPlayers = EntityQuery<HumanoidAppearanceComponent, MobStateComponent>(true);
var allZombers = GetEntityQuery<ZombieComponent>(); var allZombers = GetEntityQuery<ZombieComponent>();
var totalPlayers = new List<EntityUid>(); var totalPlayers = new List<EntityUid>();

View File

@@ -1,50 +1,32 @@
using System.Linq; using System.Linq;
using Content.Server.GameTicking;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes; using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Inventory.Events;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Tag;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.GameObjects.Components.Localization; using Robust.Shared.GameObjects.Components.Localization;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.Humanoid; namespace Content.Server.Humanoid;
public sealed partial class HumanoidSystem : SharedHumanoidSystem public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
{ {
[Dependency] private readonly MarkingManager _markingManager = default!; [Dependency] private readonly MarkingManager _markingManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<HumanoidComponent, ComponentInit>(OnInit); base.Initialize();
SubscribeLocalEvent<HumanoidComponent, HumanoidMarkingModifierMarkingSetMessage>(OnMarkingsSet); SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<HumanoidComponent, HumanoidMarkingModifierBaseLayersSetMessage>(OnBaseLayersSet); SubscribeLocalEvent<HumanoidAppearanceComponent, HumanoidMarkingModifierMarkingSetMessage>(OnMarkingsSet);
SubscribeLocalEvent<HumanoidComponent, GetVerbsEvent<Verb>>(OnVerbsRequest); SubscribeLocalEvent<HumanoidAppearanceComponent, HumanoidMarkingModifierBaseLayersSetMessage>(OnBaseLayersSet);
SubscribeLocalEvent<HumanoidComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<HumanoidAppearanceComponent, GetVerbsEvent<Verb>>(OnVerbsRequest);
SubscribeLocalEvent<HumanoidAppearanceComponent, ExaminedEvent>(OnExamined);
} }
private void Synchronize(EntityUid uid, HumanoidComponent? component = null) private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args)
{
if (!Resolve(uid, ref component))
{
return;
}
SetAppearance(uid,
component.Species,
component.CustomBaseLayers,
component.SkinColor,
component.Sex,
component.AllHiddenLayers.ToList(),
component.CurrentMarkings.GetForwardEnumerator().ToList());
}
private void OnInit(EntityUid uid, HumanoidComponent humanoid, ComponentInit args)
{ {
if (string.IsNullOrEmpty(humanoid.Species)) if (string.IsNullOrEmpty(humanoid.Species))
{ {
@@ -67,7 +49,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
LoadProfile(uid, startingSet.Profile, humanoid); LoadProfile(uid, startingSet.Profile, humanoid);
} }
private void OnExamined(EntityUid uid, HumanoidComponent component, ExaminedEvent args) private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, ExaminedEvent args)
{ {
var identity = Identity.Entity(component.Owner, EntityManager); var identity = Identity.Entity(component.Owner, EntityManager);
var species = GetSpeciesRepresentation(component.Species).ToLower(); var species = GetSpeciesRepresentation(component.Species).ToLower();
@@ -82,7 +64,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="uid">The mob's entity UID.</param> /// <param name="uid">The mob's entity UID.</param>
/// <param name="profile">The character profile to load.</param> /// <param name="profile">The character profile to load.</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidComponent? humanoid = null) public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
{ {
if (!Resolve(uid, ref humanoid)) if (!Resolve(uid, ref humanoid))
{ {
@@ -91,11 +73,11 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
SetSpecies(uid, profile.Species, false, humanoid); SetSpecies(uid, profile.Species, false, humanoid);
humanoid.Sex = profile.Sex; humanoid.Sex = profile.Sex;
humanoid.EyeColor = profile.Appearance.EyeColor;
SetSkinColor(uid, profile.Appearance.SkinColor, false); SetSkinColor(uid, profile.Appearance.SkinColor, false);
SetBaseLayerColor(uid, HumanoidVisualLayers.Eyes, profile.Appearance.EyeColor, false);
humanoid.CurrentMarkings.Clear(); humanoid.MarkingSet.Clear();
// Hair/facial hair - this may eventually be deprecated. // Hair/facial hair - this may eventually be deprecated.
@@ -117,7 +99,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
humanoid.Age = profile.Age; humanoid.Age = profile.Age;
Synchronize(uid); Dirty(humanoid);
} }
// this was done enough times that it only made sense to do it here // this was done enough times that it only made sense to do it here
@@ -129,8 +111,8 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="target">Target entity to apply the source entity's appearance to.</param> /// <param name="target">Target entity to apply the source entity's appearance to.</param>
/// <param name="sourceHumanoid">Source entity's humanoid component.</param> /// <param name="sourceHumanoid">Source entity's humanoid component.</param>
/// <param name="targetHumanoid">Target entity's humanoid component.</param> /// <param name="targetHumanoid">Target entity's humanoid component.</param>
public void CloneAppearance(EntityUid source, EntityUid target, HumanoidComponent? sourceHumanoid = null, public void CloneAppearance(EntityUid source, EntityUid target, HumanoidAppearanceComponent? sourceHumanoid = null,
HumanoidComponent? targetHumanoid = null) HumanoidAppearanceComponent? targetHumanoid = null)
{ {
if (!Resolve(source, ref sourceHumanoid) || !Resolve(target, ref targetHumanoid)) if (!Resolve(source, ref sourceHumanoid) || !Resolve(target, ref targetHumanoid))
{ {
@@ -141,7 +123,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
targetHumanoid.SkinColor = sourceHumanoid.SkinColor; targetHumanoid.SkinColor = sourceHumanoid.SkinColor;
targetHumanoid.Sex = sourceHumanoid.Sex; targetHumanoid.Sex = sourceHumanoid.Sex;
targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers); targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers);
targetHumanoid.CurrentMarkings = new(sourceHumanoid.CurrentMarkings); targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet);
targetHumanoid.Gender = sourceHumanoid.Gender; targetHumanoid.Gender = sourceHumanoid.Gender;
if (TryComp<GrammarComponent>(target, out var grammar)) if (TryComp<GrammarComponent>(target, out var grammar))
@@ -149,180 +131,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
grammar.Gender = sourceHumanoid.Gender; grammar.Gender = sourceHumanoid.Gender;
} }
Synchronize(target, targetHumanoid); Dirty(targetHumanoid);
}
/// <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, HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid) || !_prototypeManager.TryIndex<SpeciesPrototype>(species, out var prototype))
{
return;
}
humanoid.Species = species;
humanoid.CurrentMarkings.FilterSpecies(species, _markingManager);
var oldMarkings = humanoid.CurrentMarkings.GetForwardEnumerator().ToList();
humanoid.CurrentMarkings = new(oldMarkings, prototype.MarkingPoints, _markingManager, _prototypeManager);
if (sync)
{
Synchronize(uid, 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 void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid))
{
return;
}
humanoid.SkinColor = skinColor;
if (sync)
Synchronize(uid, 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,
HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid)
|| !_prototypeManager.HasIndex<HumanoidSpeciesSpriteLayer>(id))
{
return;
}
if (humanoid.CustomBaseLayers.TryGetValue(layer, out var info))
{
humanoid.CustomBaseLayers[layer] = new(id, info.Color);
}
else
{
var layerInfo = new CustomBaseLayerInfo(id, humanoid.SkinColor);
humanoid.CustomBaseLayers.Add(layer, layerInfo);
}
if (sync)
Synchronize(uid, 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, HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid))
{
return;
}
if (humanoid.CustomBaseLayers.TryGetValue(layer, out var info))
{
humanoid.CustomBaseLayers[layer] = new(info.ID, color);
}
else
{
var layerInfo = new CustomBaseLayerInfo(string.Empty, color);
humanoid.CustomBaseLayers.Add(layer, layerInfo);
}
if (sync)
Synchronize(uid, humanoid);
}
/// <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 ToggleHiddenLayer(EntityUid uid, HumanoidVisualLayers layer, HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid, false))
{
return;
}
if (humanoid.HiddenLayers.Contains(layer))
{
humanoid.HiddenLayers.Remove(layer);
}
else
{
humanoid.HiddenLayers.Add(layer);
}
Synchronize(uid, 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,
HumanoidComponent? humanoid = null)
{
if (!Resolve(uid, ref humanoid))
{
return;
}
foreach (var layer in layers)
{
if (visible)
{
if (permanent && humanoid.PermanentlyHidden.Contains(layer))
{
humanoid.PermanentlyHidden.Remove(layer);
}
humanoid.HiddenLayers.Remove(layer);
}
else
{
if (permanent)
{
humanoid.PermanentlyHidden.Add(layer);
}
humanoid.HiddenLayers.Add(layer);
}
}
Synchronize(uid, humanoid);
} }
/// <summary> /// <summary>
@@ -334,7 +143,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="sync">Whether to immediately sync this marking or not</param> /// <param name="sync">Whether to immediately sync this marking or not</param>
/// <param name="forced">If this marking was forced (ignores marking points)</param> /// <param name="forced">If this marking was forced (ignores marking points)</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidComponent? humanoid = null) public void AddMarking(EntityUid uid, string marking, Color? color = null, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null)
{ {
if (!Resolve(uid, ref humanoid) if (!Resolve(uid, ref humanoid)
|| !_markingManager.Markings.TryGetValue(marking, out var prototype)) || !_markingManager.Markings.TryGetValue(marking, out var prototype))
@@ -352,10 +161,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
} }
} }
humanoid.CurrentMarkings.AddBack(prototype.MarkingCategory, markingObject); humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject);
if (sync) if (sync)
Synchronize(uid, humanoid); Dirty(humanoid);
} }
/// <summary> /// <summary>
@@ -367,7 +176,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="sync">Whether to immediately sync this marking or not</param> /// <param name="sync">Whether to immediately sync this marking or not</param>
/// <param name="forced">If this marking was forced (ignores marking points)</param> /// <param name="forced">If this marking was forced (ignores marking points)</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void AddMarking(EntityUid uid, string marking, IReadOnlyList<Color> colors, bool sync = true, bool forced = false, HumanoidComponent? humanoid = null) public void AddMarking(EntityUid uid, string marking, IReadOnlyList<Color> colors, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null)
{ {
if (!Resolve(uid, ref humanoid) if (!Resolve(uid, ref humanoid)
|| !_markingManager.Markings.TryGetValue(marking, out var prototype)) || !_markingManager.Markings.TryGetValue(marking, out var prototype))
@@ -377,10 +186,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
var markingObject = new Marking(marking, colors); var markingObject = new Marking(marking, colors);
markingObject.Forced = forced; markingObject.Forced = forced;
humanoid.CurrentMarkings.AddBack(prototype.MarkingCategory, markingObject); humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject);
if (sync) if (sync)
Synchronize(uid, humanoid); Dirty(humanoid);
} }
/// <summary> /// <summary>
@@ -390,7 +199,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="marking">The marking to try and remove.</param> /// <param name="marking">The marking to try and remove.</param>
/// <param name="sync">Whether to immediately sync this to the humanoid</param> /// <param name="sync">Whether to immediately sync this to the humanoid</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void RemoveMarking(EntityUid uid, string marking, bool sync = true, HumanoidComponent? humanoid = null) public void RemoveMarking(EntityUid uid, string marking, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
{ {
if (!Resolve(uid, ref humanoid) if (!Resolve(uid, ref humanoid)
|| !_markingManager.Markings.TryGetValue(marking, out var prototype)) || !_markingManager.Markings.TryGetValue(marking, out var prototype))
@@ -398,10 +207,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
return; return;
} }
humanoid.CurrentMarkings.Remove(prototype.MarkingCategory, marking); humanoid.MarkingSet.Remove(prototype.MarkingCategory, marking);
if (sync) if (sync)
Synchronize(uid, humanoid); Dirty(humanoid);
} }
/// <summary> /// <summary>
@@ -411,19 +220,18 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="category">Category of the marking</param> /// <param name="category">Category of the marking</param>
/// <param name="index">Index of the marking</param> /// <param name="index">Index of the marking</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void RemoveMarking(EntityUid uid, MarkingCategories category, int index, HumanoidComponent? humanoid = null) public void RemoveMarking(EntityUid uid, MarkingCategories category, int index, HumanoidAppearanceComponent? humanoid = null)
{ {
if (index < 0 if (index < 0
|| !Resolve(uid, ref humanoid) || !Resolve(uid, ref humanoid)
|| !humanoid.CurrentMarkings.TryGetCategory(category, out var markings) || !humanoid.MarkingSet.TryGetCategory(category, out var markings)
|| index >= markings.Count) || index >= markings.Count)
{ {
return; return;
} }
humanoid.CurrentMarkings.Remove(category, index); humanoid.MarkingSet.Remove(category, index);
Dirty(humanoid);
Synchronize(uid, humanoid);
} }
/// <summary> /// <summary>
@@ -434,12 +242,12 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="index">Index of the marking</param> /// <param name="index">Index of the marking</param>
/// <param name="markingId">The marking ID to use</param> /// <param name="markingId">The marking ID to use</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void SetMarkingId(EntityUid uid, MarkingCategories category, int index, string markingId, HumanoidComponent? humanoid = null) public void SetMarkingId(EntityUid uid, MarkingCategories category, int index, string markingId, HumanoidAppearanceComponent? humanoid = null)
{ {
if (index < 0 if (index < 0
|| !_markingManager.MarkingsByCategory(category).TryGetValue(markingId, out var markingPrototype) || !_markingManager.MarkingsByCategory(category).TryGetValue(markingId, out var markingPrototype)
|| !Resolve(uid, ref humanoid) || !Resolve(uid, ref humanoid)
|| !humanoid.CurrentMarkings.TryGetCategory(category, out var markings) || !humanoid.MarkingSet.TryGetCategory(category, out var markings)
|| index >= markings.Count) || index >= markings.Count)
{ {
return; return;
@@ -451,9 +259,8 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
marking.SetColor(i, markings[index].MarkingColors[i]); marking.SetColor(i, markings[index].MarkingColors[i]);
} }
humanoid.CurrentMarkings.Replace(category, index, marking); humanoid.MarkingSet.Replace(category, index, marking);
Dirty(humanoid);
Synchronize(uid, humanoid);
} }
/// <summary> /// <summary>
@@ -465,11 +272,11 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
/// <param name="colors">The marking colors to use</param> /// <param name="colors">The marking colors to use</param>
/// <param name="humanoid">Humanoid component of the entity</param> /// <param name="humanoid">Humanoid component of the entity</param>
public void SetMarkingColor(EntityUid uid, MarkingCategories category, int index, List<Color> colors, public void SetMarkingColor(EntityUid uid, MarkingCategories category, int index, List<Color> colors,
HumanoidComponent? humanoid = null) HumanoidAppearanceComponent? humanoid = null)
{ {
if (index < 0 if (index < 0
|| !Resolve(uid, ref humanoid) || !Resolve(uid, ref humanoid)
|| !humanoid.CurrentMarkings.TryGetCategory(category, out var markings) || !humanoid.MarkingSet.TryGetCategory(category, out var markings)
|| index >= markings.Count) || index >= markings.Count)
{ {
return; return;
@@ -480,7 +287,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
markings[index].SetColor(i, colors[i]); markings[index].SetColor(i, colors[i]);
} }
Synchronize(uid, humanoid); Dirty(humanoid);
} }
/// <summary> /// <summary>
@@ -521,13 +328,13 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem
return Loc.GetString("identity-age-old"); return Loc.GetString("identity-age-old");
} }
private void EnsureDefaultMarkings(EntityUid uid, HumanoidComponent? humanoid) private void EnsureDefaultMarkings(EntityUid uid, HumanoidAppearanceComponent? humanoid)
{ {
if (!Resolve(uid, ref humanoid)) if (!Resolve(uid, ref humanoid))
{ {
return; return;
} }
humanoid.CurrentMarkings.EnsureDefault(humanoid.SkinColor, _markingManager); humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, _markingManager);
} }
} }

View File

@@ -7,12 +7,12 @@ using Robust.Server.Player;
namespace Content.Server.Humanoid; namespace Content.Server.Humanoid;
public sealed partial class HumanoidSystem public sealed partial class HumanoidAppearanceSystem
{ {
[Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
private void OnVerbsRequest(EntityUid uid, HumanoidComponent component, GetVerbsEvent<Verb> args) private void OnVerbsRequest(EntityUid uid, HumanoidAppearanceComponent component, GetVerbsEvent<Verb> args)
{ {
if (!TryComp<ActorComponent>(args.User, out var actor)) if (!TryComp<ActorComponent>(args.User, out var actor))
{ {
@@ -35,12 +35,12 @@ public sealed partial class HumanoidSystem
_uiSystem.TrySetUiState( _uiSystem.TrySetUiState(
uid, uid,
HumanoidMarkingModifierKey.Key, HumanoidMarkingModifierKey.Key,
new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers)); new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
} }
}); });
} }
private void OnBaseLayersSet(EntityUid uid, HumanoidComponent component, private void OnBaseLayersSet(EntityUid uid, HumanoidAppearanceComponent component,
HumanoidMarkingModifierBaseLayersSetMessage message) HumanoidMarkingModifierBaseLayersSetMessage message)
{ {
if (message.Session is not IPlayerSession player if (message.Session is not IPlayerSession player
@@ -55,24 +55,21 @@ public sealed partial class HumanoidSystem
} }
else else
{ {
if (!component.CustomBaseLayers.TryAdd(message.Layer, message.Info)) component.CustomBaseLayers[message.Layer] = message.Info.Value;
{
component.CustomBaseLayers[message.Layer] = message.Info;
}
} }
Synchronize(uid, component); Dirty(component);
if (message.ResendState) if (message.ResendState)
{ {
_uiSystem.TrySetUiState( _uiSystem.TrySetUiState(
uid, uid,
HumanoidMarkingModifierKey.Key, HumanoidMarkingModifierKey.Key,
new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers)); new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
} }
} }
private void OnMarkingsSet(EntityUid uid, HumanoidComponent component, private void OnMarkingsSet(EntityUid uid, HumanoidAppearanceComponent component,
HumanoidMarkingModifierMarkingSetMessage message) HumanoidMarkingModifierMarkingSetMessage message)
{ {
if (message.Session is not IPlayerSession player if (message.Session is not IPlayerSession player
@@ -81,15 +78,15 @@ public sealed partial class HumanoidSystem
return; return;
} }
component.CurrentMarkings = message.MarkingSet; component.MarkingSet = message.MarkingSet;
Synchronize(uid, component); Dirty(component);
if (message.ResendState) if (message.ResendState)
{ {
_uiSystem.TrySetUiState( _uiSystem.TrySetUiState(
uid, uid,
HumanoidMarkingModifierKey.Key, HumanoidMarkingModifierKey.Key,
new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers)); new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers));
} }
} }

View File

@@ -1,4 +1,4 @@
using Content.Server.CharacterAppearance.Components; using Content.Server.CharacterAppearance.Components;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Preferences; using Content.Shared.Preferences;
@@ -6,7 +6,7 @@ namespace Content.Server.Humanoid.Systems;
public sealed class RandomHumanoidAppearanceSystem : EntitySystem public sealed class RandomHumanoidAppearanceSystem : EntitySystem
{ {
[Dependency] private readonly HumanoidSystem _humanoid = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -18,7 +18,7 @@ public sealed class RandomHumanoidAppearanceSystem : EntitySystem
private void OnMapInit(EntityUid uid, RandomHumanoidAppearanceComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, RandomHumanoidAppearanceComponent component, MapInitEvent args)
{ {
// If we have an initial profile/base layer set, do not randomize this humanoid. // If we have an initial profile/base layer set, do not randomize this humanoid.
if (!TryComp(uid, out HumanoidComponent? humanoid) || !string.IsNullOrEmpty(humanoid.Initial)) if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid) || !string.IsNullOrEmpty(humanoid.Initial))
{ {
return; return;
} }

View File

@@ -17,7 +17,7 @@ public sealed class RandomHumanoidSystem : EntitySystem
[Dependency] private readonly IComponentFactory _compFactory = default!; [Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly HumanoidSystem _humanoid = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()

View File

@@ -118,7 +118,7 @@ public class IdentitySystem : SharedIdentitySystem
/// </summary> /// </summary>
private IdentityRepresentation GetIdentityRepresentation(EntityUid target, private IdentityRepresentation GetIdentityRepresentation(EntityUid target,
InventoryComponent? inventory=null, InventoryComponent? inventory=null,
HumanoidComponent? appearance=null) HumanoidAppearanceComponent? appearance=null)
{ {
int age = 18; int age = 18;
Gender gender = Gender.Epicene; Gender gender = Gender.Epicene;

View File

@@ -13,7 +13,7 @@ namespace Content.Server.MagicMirror;
public sealed class MagicMirrorSystem : EntitySystem public sealed class MagicMirrorSystem : EntitySystem
{ {
[Dependency] private readonly MarkingManager _markings = default!; [Dependency] private readonly MarkingManager _markings = default!;
[Dependency] private readonly HumanoidSystem _humanoid = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -30,14 +30,14 @@ public sealed class MagicMirrorSystem : EntitySystem
private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, ActivatableUIOpenAttemptEvent args) private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, ActivatableUIOpenAttemptEvent args)
{ {
if (!HasComp<HumanoidComponent>(args.User)) if (!HasComp<HumanoidAppearanceComponent>(args.User))
args.Cancel(); args.Cancel();
} }
private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component,
MagicMirrorSelectMessage message) MagicMirrorSelectMessage message)
{ {
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid)) if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
{ {
return; return;
} }
@@ -63,7 +63,7 @@ public sealed class MagicMirrorSystem : EntitySystem
private void OnMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent component, private void OnMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent component,
MagicMirrorChangeColorMessage message) MagicMirrorChangeColorMessage message)
{ {
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid)) if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
{ {
return; return;
} }
@@ -90,7 +90,7 @@ public sealed class MagicMirrorSystem : EntitySystem
private void OnMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent component, private void OnMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent component,
MagicMirrorRemoveSlotMessage message) MagicMirrorRemoveSlotMessage message)
{ {
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid)) if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
{ {
return; return;
} }
@@ -116,7 +116,7 @@ public sealed class MagicMirrorSystem : EntitySystem
private void OnMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent component, private void OnMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent component,
MagicMirrorAddSlotMessage message) MagicMirrorAddSlotMessage message)
{ {
if (message.Session.AttachedEntity == null || !TryComp<HumanoidComponent>(message.Session.AttachedEntity.Value, out var humanoid)) if (message.Session.AttachedEntity == null || !TryComp<HumanoidAppearanceComponent>(message.Session.AttachedEntity.Value, out var humanoid))
{ {
return; return;
} }
@@ -145,34 +145,34 @@ public sealed class MagicMirrorSystem : EntitySystem
UpdateInterface(uid, message.Session.AttachedEntity.Value, message.Session); UpdateInterface(uid, message.Session.AttachedEntity.Value, message.Session);
} }
private void UpdateInterface(EntityUid uid, EntityUid playerUid, ICommonSession session, HumanoidComponent? humanoid = null) private void UpdateInterface(EntityUid uid, EntityUid playerUid, ICommonSession session, HumanoidAppearanceComponent? humanoid = null)
{ {
if (!Resolve(playerUid, ref humanoid) || session is not IPlayerSession player) if (!Resolve(playerUid, ref humanoid) || session is not IPlayerSession player)
{ {
return; return;
} }
var hair = humanoid.CurrentMarkings.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings)
? new List<Marking>(hairMarkings) ? new List<Marking>(hairMarkings)
: new(); : new();
var facialHair = humanoid.CurrentMarkings.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings) var facialHair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings)
? new List<Marking>(facialHairMarkings) ? new List<Marking>(facialHairMarkings)
: new(); : new();
var msg = new MagicMirrorUiData( var msg = new MagicMirrorUiData(
humanoid.Species, humanoid.Species,
hair, hair,
humanoid.CurrentMarkings.PointsLeft(MarkingCategories.Hair) + hair.Count, humanoid.MarkingSet.PointsLeft(MarkingCategories.Hair) + hair.Count,
facialHair, facialHair,
humanoid.CurrentMarkings.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count); humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count);
_uiSystem.TrySendUiMessage(uid, MagicMirrorUiKey.Key, msg, player); _uiSystem.TrySendUiMessage(uid, MagicMirrorUiKey.Key, msg, player);
} }
private void AfterUIOpen(EntityUid uid, MagicMirrorComponent component, AfterActivatableUIOpenEvent args) private void AfterUIOpen(EntityUid uid, MagicMirrorComponent component, AfterActivatableUIOpenEvent args)
{ {
var looks = Comp<HumanoidComponent>(args.User); var looks = Comp<HumanoidAppearanceComponent>(args.User);
var actor = Comp<ActorComponent>(args.User); var actor = Comp<ActorComponent>(args.User);
UpdateInterface(uid, args.User, args.Session); UpdateInterface(uid, args.User, args.Session);

View File

@@ -26,6 +26,7 @@ using Robust.Shared.Random;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Content.Shared.Humanoid;
namespace Content.Server.Medical.BiomassReclaimer namespace Content.Server.Medical.BiomassReclaimer
{ {
@@ -256,7 +257,7 @@ namespace Content.Server.Medical.BiomassReclaimer
// Reject souled bodies in easy mode. // Reject souled bodies in easy mode.
if (_configManager.GetCVar(CCVars.BiomassEasyMode) && if (_configManager.GetCVar(CCVars.BiomassEasyMode) &&
HasComp<HumanoidComponent>(dragged) && HasComp<HumanoidAppearanceComponent>(dragged) &&
TryComp<MindComponent>(dragged, out var mindComp)) TryComp<MindComponent>(dragged, out var mindComp))
{ {
if (mindComp.Mind?.UserId != null && _playerManager.TryGetSessionById(mindComp.Mind.UserId.Value, out _)) if (mindComp.Mind?.UserId != null && _playerManager.TryGetSessionById(mindComp.Mind.UserId.Value, out _))

View File

@@ -32,7 +32,7 @@ namespace Content.Server.Polymorph.Systems
[Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly HumanoidSystem _humanoid = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly ContainerSystem _container = default!;
public override void Initialize() public override void Initialize()

View File

@@ -276,7 +276,7 @@ namespace Content.Server.Preferences.Managers
case HumanoidCharacterProfile hp: case HumanoidCharacterProfile hp:
{ {
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var selectedSpecies = HumanoidSystem.DefaultSpecies; var selectedSpecies = HumanoidAppearanceSystem.DefaultSpecies;
if (prototypeManager.TryIndex<SpeciesPrototype>(hp.Species, out var species) && species.RoundStart) if (prototypeManager.TryIndex<SpeciesPrototype>(hp.Species, out var species) && species.RoundStart)
{ {

View File

@@ -29,6 +29,7 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Revenant.Components; using Content.Shared.Revenant.Components;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Content.Shared.Humanoid;
namespace Content.Server.Revenant.EntitySystems; namespace Content.Server.Revenant.EntitySystems;
@@ -70,7 +71,7 @@ public sealed partial class RevenantSystem
return; return;
} }
if (!HasComp<MobStateComponent>(target) || !HasComp<HumanoidComponent>(target) || HasComp<RevenantComponent>(target)) if (!HasComp<MobStateComponent>(target) || !HasComp<HumanoidAppearanceComponent>(target) || HasComp<RevenantComponent>(target))
return; return;
args.Handled = true; args.Handled = true;

View File

@@ -70,7 +70,7 @@ public sealed class VocalSystem : EntitySystem
if (!_blocker.CanSpeak(uid)) if (!_blocker.CanSpeak(uid))
return false; return false;
var sex = CompOrNull<HumanoidComponent>(uid)?.Sex ?? Sex.Unsexed; var sex = CompOrNull<HumanoidAppearanceComponent>(uid)?.Sex ?? Sex.Unsexed;
if (_random.Prob(component.WilhelmProbability)) if (_random.Prob(component.WilhelmProbability))
{ {

View File

@@ -34,7 +34,7 @@ public sealed class StationSpawningSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly HandsSystem _handsSystem = default!; [Dependency] private readonly HandsSystem _handsSystem = default!;
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly IdCardSystem _cardSystem = default!; [Dependency] private readonly IdCardSystem _cardSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly PDASystem _pdaSystem = default!; [Dependency] private readonly PDASystem _pdaSystem = default!;
@@ -106,7 +106,7 @@ public sealed class StationSpawningSystem : EntitySystem
} }
var entity = EntityManager.SpawnEntity( var entity = EntityManager.SpawnEntity(
_prototypeManager.Index<SpeciesPrototype>(profile?.Species ?? HumanoidSystem.DefaultSpecies).Prototype, _prototypeManager.Index<SpeciesPrototype>(profile?.Species ?? HumanoidAppearanceSystem.DefaultSpecies).Prototype,
coordinates); coordinates);
if (job?.StartingGear != null) if (job?.StartingGear != null)

View File

@@ -3,6 +3,7 @@ using Content.Shared.Humanoid;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Humanoid.Prototypes; using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
using Content.Shared.Humanoid;
namespace Content.Server.Store.Conditions; namespace Content.Server.Store.Conditions;
@@ -28,7 +29,7 @@ public sealed class BuyerSpeciesCondition : ListingCondition
{ {
var ent = args.EntityManager; var ent = args.EntityManager;
if (!ent.TryGetComponent<HumanoidComponent>(args.Buyer, out var appearance)) if (!ent.TryGetComponent<HumanoidAppearanceComponent>(args.Buyer, out var appearance))
return true; // inanimate or non-humanoid entities should be handled elsewhere, main example being surplus crates return true; // inanimate or non-humanoid entities should be handled elsewhere, main example being surplus crates
if (Blacklist != null) if (Blacklist != null)

View File

@@ -34,7 +34,7 @@ namespace Content.Server.Zombies
[Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly HumanoidSystem _humanoidSystem = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
public override void Initialize() public override void Initialize()
{ {

View File

@@ -46,7 +46,7 @@ namespace Content.Server.Zombies
[Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly ServerInventorySystem _serverInventory = default!; [Dependency] private readonly ServerInventorySystem _serverInventory = default!;
[Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly HumanoidSystem _sharedHuApp = default!; [Dependency] private readonly HumanoidAppearanceSystem _sharedHuApp = default!;
[Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly IChatManager _chatMan = default!;
@@ -125,7 +125,7 @@ namespace Content.Server.Zombies
Dirty(melee); Dirty(melee);
//We have specific stuff for humanoid zombies because they matter more //We have specific stuff for humanoid zombies because they matter more
if (TryComp<HumanoidComponent>(target, out var huApComp)) //huapcomp if (TryComp<HumanoidAppearanceComponent>(target, out var huApComp)) //huapcomp
{ {
//store some values before changing them in case the humanoid get cloned later //store some values before changing them in case the humanoid get cloned later
zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor; zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor;

View File

@@ -1,7 +1,9 @@
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Humanoid;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Tag;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
namespace Content.Shared.Clothing.EntitySystems; namespace Content.Shared.Clothing.EntitySystems;
@@ -9,6 +11,8 @@ namespace Content.Shared.Clothing.EntitySystems;
public abstract class ClothingSystem : EntitySystem public abstract class ClothingSystem : EntitySystem
{ {
[Dependency] private readonly SharedItemSystem _itemSys = default!; [Dependency] private readonly SharedItemSystem _itemSys = default!;
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -23,11 +27,15 @@ public abstract class ClothingSystem : EntitySystem
protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args) protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args)
{ {
component.InSlot = args.Slot; component.InSlot = args.Slot;
if (args.Slot == "head" && _tagSystem.HasTag(args.Equipment, "HidesHair"))
_humanoidSystem.SetLayerVisibility(args.Equipee, HumanoidVisualLayers.Hair, false);
} }
protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args) protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
{ {
component.InSlot = null; component.InSlot = null;
if (args.Slot == "head" && _tagSystem.HasTag(args.Equipment, "HidesHair"))
_humanoidSystem.SetLayerVisibility(args.Equipee, HumanoidVisualLayers.Hair, true);
} }
private void OnGetState(EntityUid uid, ClothingComponent component, ref ComponentGetState args) private void OnGetState(EntityUid uid, ClothingComponent component, ref ComponentGetState args)

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> /// <summary>
/// Sublayers. Any other layers that may visually depend on this layer existing. /// 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. /// 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 namespace Content.Shared.Humanoid.Markings
{ {
[DataDefinition]
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class Marking : IEquatable<Marking>, IComparable<Marking>, IComparable<string> public sealed class Marking : IEquatable<Marking>, IComparable<Marking>, IComparable<string>
{ {
@@ -54,7 +55,7 @@ namespace Content.Shared.Humanoid.Markings
/// <summary> /// <summary>
/// If this marking is currently visible. /// If this marking is currently visible.
/// </summary> /// </summary>
[ViewVariables] [DataField("visible")]
public bool Visible = true; public bool Visible = true;
/// <summary> /// <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. /// 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. /// Most APIs that accept a set of markings usually use a List of type Marking instead.
/// </remarks> /// </remarks>
[DataDefinition]
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class MarkingSet 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 /// feature of markings, which is the limit of markings you can put on a
/// humanoid. /// humanoid.
/// </remarks> /// </remarks>
private Dictionary<MarkingCategories, List<Marking>> _markings = new(); [DataField("markings")]
public Dictionary<MarkingCategories, List<Marking>> Markings = new();
// why i didn't encapsulate this in the first place, i won't know
/// <summary> /// <summary>
/// Marking points for each category. /// Marking points for each category.
/// </summary> /// </summary>
private Dictionary<MarkingCategories, MarkingPoints> _points = new(); [DataField("points")]
public Dictionary<MarkingCategories, MarkingPoints> Points = new();
public IReadOnlyList<Marking> this[MarkingCategories category] => _markings[category];
public MarkingSet() public MarkingSet()
{} {}
@@ -71,7 +70,7 @@ public sealed class MarkingSet
return; return;
} }
_points = MarkingPoints.CloneMarkingPointDictionary(points.Points); Points = MarkingPoints.CloneMarkingPointDictionary(points.Points);
foreach (var marking in markings) foreach (var marking in markings)
{ {
@@ -111,7 +110,7 @@ public sealed class MarkingSet
/// <param name="other">The other marking set.</param> /// <param name="other">The other marking set.</param>
public MarkingSet(MarkingSet other) public MarkingSet(MarkingSet other)
{ {
foreach (var (key, list) in other._markings) foreach (var (key, list) in other.Markings)
{ {
foreach (var marking in list) foreach (var marking in list)
{ {
@@ -119,7 +118,7 @@ public sealed class MarkingSet
} }
} }
_points = MarkingPoints.CloneMarkingPointDictionary(other._points); Points = MarkingPoints.CloneMarkingPointDictionary(other.Points);
} }
/// <summary> /// <summary>
@@ -137,7 +136,7 @@ public sealed class MarkingSet
var speciesProto = prototypeManager.Index<SpeciesPrototype>(species); var speciesProto = prototypeManager.Index<SpeciesPrototype>(species);
var onlyWhitelisted = prototypeManager.Index<MarkingPointsPrototype>(speciesProto.MarkingPoints).OnlyWhitelisted; 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) foreach (var marking in list)
{ {
@@ -175,7 +174,7 @@ public sealed class MarkingSet
IoCManager.Resolve(ref markingManager); IoCManager.Resolve(ref markingManager);
var toRemove = new List<int>(); 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++) for (var i = 0; i < list.Count; i++)
{ {
@@ -207,7 +206,7 @@ public sealed class MarkingSet
{ {
IoCManager.Resolve(ref markingManager); IoCManager.Resolve(ref markingManager);
foreach (var (category, points) in _points) foreach (var (category, points) in Points)
{ {
if (points.Points <= 0 || points.DefaultMarkings.Count <= 0) 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> /// <returns>A number equal or greater than zero if the category exists, -1 otherwise.</returns>
public int PointsLeft(MarkingCategories category) public int PointsLeft(MarkingCategories category)
{ {
if (!_points.TryGetValue(category, out var points)) if (!Points.TryGetValue(category, out var points))
{ {
return -1; return -1;
} }
@@ -266,7 +265,7 @@ public sealed class MarkingSet
/// <param name="marking">The marking instance in question.</param> /// <param name="marking">The marking instance in question.</param>
public void AddFront(MarkingCategories category, Marking marking) 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) if (points.Points <= 0)
{ {
@@ -276,10 +275,10 @@ public sealed class MarkingSet
points.Points--; points.Points--;
} }
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
markings = new(); markings = new();
_markings[category] = markings; Markings[category] = markings;
} }
markings.Insert(0, marking); markings.Insert(0, marking);
@@ -292,7 +291,7 @@ public sealed class MarkingSet
/// <param name="marking"></param> /// <param name="marking"></param>
public void AddBack(MarkingCategories category, Marking marking) 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) if (points.Points <= 0)
{ {
@@ -302,10 +301,10 @@ public sealed class MarkingSet
points.Points--; points.Points--;
} }
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
markings = new(); markings = new();
_markings[category] = markings; Markings[category] = markings;
} }
@@ -320,7 +319,7 @@ public sealed class MarkingSet
public List<Marking> AddCategory(MarkingCategories category) public List<Marking> AddCategory(MarkingCategories category)
{ {
var markings = new List<Marking>(); var markings = new List<Marking>();
_markings.Add(category, markings); Markings.Add(category, markings);
return markings; return markings;
} }
@@ -332,7 +331,7 @@ public sealed class MarkingSet
/// <param name="marking">The marking to insert.</param> /// <param name="marking">The marking to insert.</param>
public void Replace(MarkingCategories category, int index, Marking marking) 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) || index >= markings.Count)
{ {
return; return;
@@ -349,7 +348,7 @@ public sealed class MarkingSet
/// <returns>True if removed, false otherwise.</returns> /// <returns>True if removed, false otherwise.</returns>
public bool Remove(MarkingCategories category, string id) public bool Remove(MarkingCategories category, string id)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return false; return false;
} }
@@ -361,7 +360,7 @@ public sealed class MarkingSet
continue; continue;
} }
if (!markings[i].Forced && _points.TryGetValue(category, out var points)) if (!markings[i].Forced && Points.TryGetValue(category, out var points))
{ {
points.Points++; points.Points++;
} }
@@ -381,7 +380,7 @@ public sealed class MarkingSet
/// <returns>True if removed, false otherwise.</returns> /// <returns>True if removed, false otherwise.</returns>
public void Remove(MarkingCategories category, int idx) public void Remove(MarkingCategories category, int idx)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return; return;
} }
@@ -391,7 +390,7 @@ public sealed class MarkingSet
return; return;
} }
if (!markings[idx].Forced && _points.TryGetValue(category, out var points)) if (!markings[idx].Forced && Points.TryGetValue(category, out var points))
{ {
points.Points++; points.Points++;
} }
@@ -406,12 +405,12 @@ public sealed class MarkingSet
/// <returns>True if removed, false otherwise.</returns> /// <returns>True if removed, false otherwise.</returns>
public bool RemoveCategory(MarkingCategories category) public bool RemoveCategory(MarkingCategories category)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return false; return false;
} }
if (_points.TryGetValue(category, out var points)) if (Points.TryGetValue(category, out var points))
{ {
foreach (var marking in markings) foreach (var marking in markings)
{ {
@@ -424,7 +423,7 @@ public sealed class MarkingSet
} }
} }
_markings.Remove(category); Markings.Remove(category);
return true; return true;
} }
@@ -447,7 +446,7 @@ public sealed class MarkingSet
/// <returns>The index of the marking, otherwise a negative number.</returns> /// <returns>The index of the marking, otherwise a negative number.</returns>
public int FindIndexOf(MarkingCategories category, string id) public int FindIndexOf(MarkingCategories category, string id)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return -1; return -1;
} }
@@ -465,7 +464,7 @@ public sealed class MarkingSet
{ {
markings = null; markings = null;
if (_markings.TryGetValue(category, out var list)) if (Markings.TryGetValue(category, out var list))
{ {
markings = list; markings = list;
return true; return true;
@@ -485,7 +484,7 @@ public sealed class MarkingSet
{ {
marking = null; marking = null;
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return false; return false;
} }
@@ -509,7 +508,7 @@ public sealed class MarkingSet
/// <param name="idx">Index of the marking.</param> /// <param name="idx">Index of the marking.</param>
public void ShiftRankUp(MarkingCategories category, int idx) public void ShiftRankUp(MarkingCategories category, int idx)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return; return;
} }
@@ -529,7 +528,7 @@ public sealed class MarkingSet
/// <param name="idx">Index of the marking from the end</param> /// <param name="idx">Index of the marking from the end</param>
public void ShiftRankUpFromEnd(MarkingCategories category, int idx) public void ShiftRankUpFromEnd(MarkingCategories category, int idx)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return; return;
} }
@@ -544,7 +543,7 @@ public sealed class MarkingSet
/// <param name="idx">Index of the marking.</param> /// <param name="idx">Index of the marking.</param>
public void ShiftRankDown(MarkingCategories category, int idx) public void ShiftRankDown(MarkingCategories category, int idx)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return; return;
} }
@@ -564,7 +563,7 @@ public sealed class MarkingSet
/// <param name="idx">Index of the marking from the end</param> /// <param name="idx">Index of the marking from the end</param>
public void ShiftRankDownFromEnd(MarkingCategories category, int idx) public void ShiftRankDownFromEnd(MarkingCategories category, int idx)
{ {
if (!_markings.TryGetValue(category, out var markings)) if (!Markings.TryGetValue(category, out var markings))
{ {
return; return;
} }
@@ -579,7 +578,7 @@ public sealed class MarkingSet
public ForwardMarkingEnumerator GetForwardEnumerator() public ForwardMarkingEnumerator GetForwardEnumerator()
{ {
var markings = new List<Marking>(); var markings = new List<Marking>();
foreach (var (_, list) in _markings) foreach (var (_, list) in Markings)
{ {
markings.AddRange(list); markings.AddRange(list);
} }
@@ -595,7 +594,7 @@ public sealed class MarkingSet
public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category) public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category)
{ {
var markings = new List<Marking>(); var markings = new List<Marking>();
if (_markings.TryGetValue(category, out var listing)) if (Markings.TryGetValue(category, out var listing))
{ {
markings = new(listing); markings = new(listing);
} }
@@ -610,7 +609,7 @@ public sealed class MarkingSet
public ReverseMarkingEnumerator GetReverseEnumerator() public ReverseMarkingEnumerator GetReverseEnumerator()
{ {
var markings = new List<Marking>(); var markings = new List<Marking>();
foreach (var (_, list) in _markings) foreach (var (_, list) in Markings)
{ {
markings.AddRange(list); markings.AddRange(list);
} }
@@ -626,7 +625,7 @@ public sealed class MarkingSet
public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category) public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category)
{ {
var markings = new List<Marking>(); var markings = new List<Marking>();
if (_markings.TryGetValue(category, out var listing)) if (Markings.TryGetValue(category, out var listing))
{ {
markings = new(listing); markings = new(listing);
} }
@@ -636,8 +635,8 @@ public sealed class MarkingSet
public bool CategoryEquals(MarkingCategories category, MarkingSet other) public bool CategoryEquals(MarkingCategories category, MarkingSet other)
{ {
if (!_markings.TryGetValue(category, out var markings) if (!Markings.TryGetValue(category, out var markings)
|| !other._markings.TryGetValue(category, out var markingsOther)) || !other.Markings.TryGetValue(category, out var markingsOther))
{ {
return false; return false;
} }
@@ -647,7 +646,7 @@ public sealed class MarkingSet
public bool Equals(MarkingSet other) public bool Equals(MarkingSet other)
{ {
foreach (var (category, _) in _markings) foreach (var (category, _) in Markings)
{ {
if (!CategoryEquals(category, other)) if (!CategoryEquals(category, other))
{ {
@@ -665,7 +664,7 @@ public sealed class MarkingSet
/// <returns>Enumerator of marking categories that were different between the two.</returns> /// <returns>Enumerator of marking categories that were different between the two.</returns>
public IEnumerable<MarkingCategories> CategoryDifference(MarkingSet other) public IEnumerable<MarkingCategories> CategoryDifference(MarkingSet other)
{ {
foreach (var (category, _) in _markings) foreach (var (category, _) in Markings)
{ {
if (!CategoryEquals(category, other)) if (!CategoryEquals(category, other))
{ {

View File

@@ -1,5 +1,6 @@
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Shared.Humanoid.Prototypes; 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 Content.Shared.Humanoid.Markings;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Shared.Humanoid; namespace Content.Shared.Humanoid;
@@ -40,6 +41,7 @@ public sealed class HumanoidMarkingModifierBaseLayersSetMessage : BoundUserInter
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class HumanoidMarkingModifierState : BoundUserInterfaceState 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) public HumanoidMarkingModifierState(MarkingSet markingSet, string species, Color skinColor, Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo> customBaseLayers)
{ {
MarkingSet = markingSet; 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);
}
}

View File

@@ -99,7 +99,7 @@ namespace Content.Shared.Preferences
/// <summary> /// <summary>
/// Get the default humanoid character profile, using internal constant values. /// Get the default humanoid character profile, using internal constant values.
/// Defaults to <see cref="SharedHumanoidSystem.DefaultSpecies"/> for the species. /// Defaults to <see cref="SharedHumanoidAppearanceSystem.DefaultSpecies"/> for the species.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static HumanoidCharacterProfile Default() public static HumanoidCharacterProfile Default()
@@ -107,7 +107,7 @@ namespace Content.Shared.Preferences
return new( return new(
"John Doe", "John Doe",
"", "",
SharedHumanoidSystem.DefaultSpecies, SharedHumanoidAppearanceSystem.DefaultSpecies,
18, 18,
Sex.Male, Sex.Male,
Gender.Male, Gender.Male,
@@ -126,9 +126,9 @@ namespace Content.Shared.Preferences
/// <summary> /// <summary>
/// Return a default character profile, based on species. /// Return a default character profile, based on species.
/// </summary> /// </summary>
/// <param name="species">The species to use in this default profile. The default species is <see cref="SharedHumanoidSystem.DefaultSpecies"/>.</param> /// <param name="species">The species to use in this default profile. The default species is <see cref="SharedHumanoidAppearanceSystem.DefaultSpecies"/>.</param>
/// <returns>Humanoid character profile with default settings.</returns> /// <returns>Humanoid character profile with default settings.</returns>
public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies) public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies)
{ {
return new( return new(
"John Doe", "John Doe",
@@ -164,7 +164,7 @@ namespace Content.Shared.Preferences
return RandomWithSpecies(species); return RandomWithSpecies(species);
} }
public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies) public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies)
{ {
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var random = IoCManager.Resolve<IRobustRandom>(); var random = IoCManager.Resolve<IRobustRandom>();

View File

@@ -3,6 +3,7 @@ using Content.Shared.Humanoid;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using static Content.Shared.Humanoid.HumanoidAppearanceState;
namespace Content.Shared.Zombies namespace Content.Shared.Zombies
{ {

View File

@@ -32,7 +32,7 @@
- MobMask - MobMask
layer: layer:
- MachineLayer - MachineLayer
- type: Humanoid - type: HumanoidAppearance
- type: AnimationPlayer - type: AnimationPlayer
- type: MeleeWeapon - type: MeleeWeapon
hidden: true hidden: true

View File

@@ -173,7 +173,7 @@
heatDamage: heatDamage:
types: types:
Heat: 0.1 #per second, scales with temperature & other constants Heat: 0.1 #per second, scales with temperature & other constants
- type: Humanoid - type: HumanoidAppearance
species: Human species: Human
- type: Body - type: Body
prototype: Human prototype: Human
@@ -364,7 +364,7 @@
- MobMask - MobMask
layer: layer:
- MobLayer - MobLayer
- type: Humanoid - type: HumanoidAppearance
species: Human species: Human
- type: Body - type: Body
prototype: Human prototype: Human

View File

@@ -5,7 +5,7 @@
id: BaseMobDiona id: BaseMobDiona
abstract: true abstract: true
components: components:
- type: Humanoid - type: HumanoidAppearance
species: Diona species: Diona
- type: Hunger - type: Hunger
- type: Thirst - type: Thirst
@@ -61,5 +61,5 @@
components: components:
- type: Inventory - type: Inventory
templateId: diona templateId: diona
- type: Humanoid - type: HumanoidAppearance
species: Diona species: Diona

View File

@@ -5,7 +5,7 @@
id: BaseMobReptilian id: BaseMobReptilian
abstract: true abstract: true
components: components:
- type: Humanoid - type: HumanoidAppearance
species: Reptilian species: Reptilian
- type: Hunger - type: Hunger
- type: Thirst - type: Thirst
@@ -56,7 +56,7 @@
noSpawn: true noSpawn: true
description: A dummy reptilian meant to be used in character setup. description: A dummy reptilian meant to be used in character setup.
components: components:
- type: Humanoid - type: HumanoidAppearance
species: Reptilian species: Reptilian
#Weh #Weh

View File

@@ -5,7 +5,7 @@
id: BaseMobSkeletonPerson id: BaseMobSkeletonPerson
abstract: true abstract: true
components: components:
- type: Humanoid - type: HumanoidAppearance
species: Skeleton species: Skeleton
- type: Icon - type: Icon
sprite: Mobs/Species/Skeleton/parts.rsi sprite: Mobs/Species/Skeleton/parts.rsi
@@ -70,5 +70,5 @@
noSpawn: true noSpawn: true
description: A dummy skeleton meant to be used in character setup. description: A dummy skeleton meant to be used in character setup.
components: components:
- type: Humanoid - type: HumanoidAppearance
species: Skeleton species: Skeleton

View File

@@ -12,7 +12,7 @@
- type: Body - type: Body
prototype: Slime prototype: Slime
requiredLegs: 2 requiredLegs: 2
- type: Humanoid - type: HumanoidAppearance
species: SlimePerson species: SlimePerson
- type: Speech - type: Speech
speechSounds: Slime speechSounds: Slime
@@ -81,5 +81,5 @@
noSpawn: true noSpawn: true
description: A dummy slime meant to be used in character setup. description: A dummy slime meant to be used in character setup.
components: components:
- type: Humanoid - type: HumanoidAppearance
species: SlimePerson species: SlimePerson

View File

@@ -90,7 +90,7 @@
damageRecovery: damageRecovery:
types: types:
Asphyxiation: -1.0 Asphyxiation: -1.0
- type: Humanoid - type: HumanoidAppearance
species: Vox species: Vox
# canColorHair: false # canColorHair: false
# canColorFacialHair: false # canColorFacialHair: false
@@ -107,7 +107,7 @@
parent: MobHumanDummy parent: MobHumanDummy
noSpawn: true noSpawn: true
components: components:
- type: Humanoid - type: HumanoidAppearance
species: Vox species: Vox
- type: Body - type: Body
prototype: Vox prototype: Vox