diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index ec98dd115e..816d51fb19 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -65,9 +65,8 @@ public sealed class ClientClothingSystem : ClothingSystem if (!TryComp(uid, out SpriteComponent? sprite) || !sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer)) return; - if (!args.AppearanceData.TryGetValue(HumanoidVisualizerKey.Key, out object? obj) - || obj is not HumanoidVisualizerData data - || data.Sex != Sex.Female + if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid) + || humanoid.Sex != Sex.Female || !_inventorySystem.TryGetSlotEntity(uid, "jumpsuit", out var suit, component) || !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 (_appearance.TryGetData(equipee, HumanoidVisualizerKey.Key, out var data) - && data.Sex == Sex.Female) + if (TryComp(equipee, out HumanoidAppearanceComponent? humanoid) && humanoid.Sex == Sex.Female) { sprite.LayerSetState(suitLayer, clothingComponent.FemaleMask switch { diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs new file mode 100644 index 0000000000..0d58eb9663 --- /dev/null +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -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(OnHandleState); + } + + private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentHandleState args) + { + if (args.Current is not HumanoidAppearanceState state) + return; + + ApplyState(uid, component, Comp(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(component.BaseLayers.Keys); + component.BaseLayers.Clear(); + + // add default species layers + var speciesProto = _prototypeManager.Index(component.Species); + var baseSprites = _prototypeManager.Index(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(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); + } + + /// + /// Loads a profile directly into a humanoid. + /// + /// The humanoid entity's UID + /// The profile to load. + /// The humanoid entity's humanoid component. + /// + /// 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. + /// + public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) + { + if (!Resolve(uid, ref humanoid)) + { + return; + } + + var customBaseLayers = new Dictionary(); + + var speciesPrototype = _prototypeManager.Index(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(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 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? 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(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); + } + } + } +} diff --git a/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs b/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs index 75942ba56d..c493b46a38 100644 --- a/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs +++ b/Content.Client/Humanoid/HumanoidMarkingModifierBoundUserInterface.cs @@ -1,6 +1,7 @@ using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Robust.Client.GameObjects; +using static Content.Shared.Humanoid.HumanoidAppearanceState; namespace Content.Client.Humanoid; diff --git a/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml.cs b/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml.cs index 2f6c396096..bc6c3338e2 100644 --- a/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml.cs +++ b/Content.Client/Humanoid/HumanoidMarkingModifierWindow.xaml.cs @@ -4,6 +4,7 @@ using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; +using static Content.Shared.Humanoid.HumanoidAppearanceState; namespace Content.Client.Humanoid; @@ -63,7 +64,7 @@ public sealed partial class HumanoidMarkingModifierWindow : DefaultWindow continue; } - modifier.SetState(true, layerInfo.ID, layerInfo.Color); + modifier.SetState(true, layerInfo.ID, layerInfo.Color ?? Color.White); } } diff --git a/Content.Client/Humanoid/HumanoidSystem.cs b/Content.Client/Humanoid/HumanoidSystem.cs deleted file mode 100644 index 125a332dee..0000000000 --- a/Content.Client/Humanoid/HumanoidSystem.cs +++ /dev/null @@ -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!; - - /// - /// Loads a profile directly into a humanoid. - /// - /// The humanoid entity's UID - /// The profile to load. - /// The humanoid entity's humanoid component. - /// - /// 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. - /// - 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.Eyes] = new CustomBaseLayerInfo(string.Empty, profile.Appearance.EyeColor) - }; - - var speciesPrototype = _prototypeManager.Index(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()); - } -} diff --git a/Content.Client/Humanoid/HumanoidVisualizerSystem.cs b/Content.Client/Humanoid/HumanoidVisualizerSystem.cs deleted file mode 100644 index 5539ec27d1..0000000000 --- a/Content.Client/Humanoid/HumanoidVisualizerSystem.cs +++ /dev/null @@ -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 -{ - [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 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 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 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(); - 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 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 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? 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 baseSprites, - Dictionary? customBaseSprites, - HumanoidComponent humanoid) - { - var newBaseLayers = new Dictionary(); - - 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 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; - } - } - } - } -} diff --git a/Content.Client/Humanoid/MarkingPicker.xaml.cs b/Content.Client/Humanoid/MarkingPicker.xaml.cs index 7a6babc9af..6d9e94f7f6 100644 --- a/Content.Client/Humanoid/MarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/MarkingPicker.xaml.cs @@ -34,7 +34,7 @@ public sealed partial class MarkingPicker : Control private List _markingCategories = Enum.GetValues().ToList(); - private string _currentSpecies = SharedHumanoidSystem.DefaultSpecies; + private string _currentSpecies = SharedHumanoidAppearanceSystem.DefaultSpecies; public Color CurrentSkinColor = Color.White; private readonly HashSet _ignoreCategories = new(); @@ -361,12 +361,13 @@ public sealed partial class MarkingPicker : Control colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" }); 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( - listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].RByte, - listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].GByte, - listing[listing.Count - 1 - item.ItemIndex].MarkingColors[i].BByte + color.RByte, + color.GByte, + color.BByte ); colorSelector.Color = currentColor; _currentMarkingColors.Add(currentColor); @@ -394,7 +395,7 @@ public sealed partial class MarkingPicker : Control _selectedMarking.IconModulate = _currentMarkingColors[colorIndex]; - var marking = new Marking(_currentMarkings[_selectedMarkingCategory][markingIndex]); + var marking = new Marking(_currentMarkings.Markings[_selectedMarkingCategory][markingIndex]); marking.SetColor(colorIndex, _currentMarkingColors[colorIndex]); _currentMarkings.Replace(_selectedMarkingCategory, markingIndex, marking); diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs index 02670f7774..67ee82aefb 100644 --- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs +++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs @@ -128,7 +128,7 @@ namespace Content.Client.Lobby.UI _viewBox.AddChild(viewWest); _viewBox.AddChild(viewEast); _summaryLabel.Text = selectedCharacter.Summary; - EntitySystem.Get().LoadProfile(_previewDummy.Value, selectedCharacter); + EntitySystem.Get().LoadProfile(_previewDummy.Value, selectedCharacter); GiveDummyJobClothes(_previewDummy.Value, selectedCharacter); } } diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs index e979650528..d5230228e9 100644 --- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs +++ b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs @@ -168,10 +168,10 @@ namespace Content.Client.Preferences.UI } else { - _previewDummy = entityManager.SpawnEntity(prototypeManager.Index(SharedHumanoidSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); + _previewDummy = entityManager.SpawnEntity(prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); } - EntitySystem.Get().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile); + EntitySystem.Get().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile); if (humanoid != null) { diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs index 64af950e28..fcfbea106d 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs @@ -553,7 +553,7 @@ namespace Content.Client.Preferences.UI #endregion FlavorText #region Dummy - var species = Profile?.Species ?? SharedHumanoidSystem.DefaultSpecies; + var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies; var dollProto = _prototypeManager.Index(species).DollPrototype; if (_previewDummy != null) @@ -693,7 +693,7 @@ namespace Content.Client.Preferences.UI private void RebuildSpriteView() { - var species = Profile?.Species ?? SharedHumanoidSystem.DefaultSpecies; + var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies; var dollProto = _prototypeManager.Index(species).DollPrototype; if (_previewDummy != null) @@ -1032,7 +1032,7 @@ namespace Content.Client.Preferences.UI if (Profile is null) return; - EntitySystem.Get().LoadProfile(_previewDummy!.Value, Profile); + EntitySystem.Get().LoadProfile(_previewDummy!.Value, Profile); LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile); } diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index 1aa4f78b5a..472b6b8acd 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -25,7 +25,7 @@ public sealed class BodySystem : SharedBodySystem { [Dependency] private readonly GameTicker _ticker = 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 SharedAudioSystem _audio = default!; @@ -83,7 +83,7 @@ public sealed class BodySystem : SharedBodySystem return false; if (part.Body is { } body && - TryComp(body, out var humanoid)) + TryComp(body, out var humanoid)) { var layer = part.ToHumanoidLayers(); if (layer != null) @@ -103,7 +103,7 @@ public sealed class BodySystem : SharedBodySystem if (!base.DropPart(partId, part)) return false; - if (oldBody == null || !TryComp(oldBody, out var humanoid)) + if (oldBody == null || !TryComp(oldBody, out var humanoid)) return true; var layer = part.ToHumanoidLayers(); diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index df4d3bb86d..45ffc4a967 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -31,6 +31,7 @@ using Robust.Shared.Random; using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; +using Content.Shared.Humanoid; namespace Content.Server.Cloning { @@ -41,7 +42,7 @@ namespace Content.Server.Cloning [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly EuiManager _euiManager = null!; [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 MobStateSystem _mobStateSystem = 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)) return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. - if (!TryComp(bodyToClone, out var humanoid)) + if (!TryComp(bodyToClone, out var humanoid)) return false; // whatever body was to be cloned, was not a humanoid if (!_prototype.TryIndex(humanoid.Species, out var speciesPrototype)) diff --git a/Content.Server/Clothing/ClothingSystem.cs b/Content.Server/Clothing/ClothingSystem.cs index 93223838f6..17ad016198 100644 --- a/Content.Server/Clothing/ClothingSystem.cs +++ b/Content.Server/Clothing/ClothingSystem.cs @@ -1,39 +1,7 @@ -using Content.Server.Humanoid; -using Content.Shared.Clothing.Components; using Content.Shared.Clothing.EntitySystems; -using Content.Shared.Humanoid; -using Content.Shared.Inventory.Events; -using Content.Shared.Tag; namespace Content.Server.Clothing; 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); - } - } } diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index a7ba11a3c6..adc6ab92da 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -303,7 +303,7 @@ namespace Content.Server.Dragon var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate); //Humanoid devours allow dragon to get eggs, corpses included - if (!EntityManager.HasComponent(args.Target)) + if (!EntityManager.HasComponent(args.Target)) { ichorInjection.ScaleSolution(0.5f); } diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index 22f1f2da8d..f51a22b42d 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -148,7 +148,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem private void CheckRoundEnd(EntityUid target) { //we only care about players, not monkeys and such. - if (!HasComp(target)) + if (!HasComp(target)) return; var percent = GetInfectedPercentage(out var num); @@ -196,7 +196,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem private float GetInfectedPercentage(out List livingHumans) { - var allPlayers = EntityQuery(true); + var allPlayers = EntityQuery(true); var allZombers = GetEntityQuery(); var totalPlayers = new List(); diff --git a/Content.Server/Humanoid/Systems/HumanoidSystem.cs b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs similarity index 52% rename from Content.Server/Humanoid/Systems/HumanoidSystem.cs rename to Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs index 51060c8634..cf4d20efee 100644 --- a/Content.Server/Humanoid/Systems/HumanoidSystem.cs +++ b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs @@ -1,50 +1,32 @@ using System.Linq; -using Content.Server.GameTicking; using Content.Shared.Examine; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.IdentityManagement; -using Content.Shared.Inventory.Events; using Content.Shared.Preferences; -using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.GameObjects.Components.Localization; using Robust.Shared.Prototypes; 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 IPrototypeManager _prototypeManager = default!; public override void Initialize() { - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnMarkingsSet); - SubscribeLocalEvent(OnBaseLayersSet); - SubscribeLocalEvent>(OnVerbsRequest); - SubscribeLocalEvent(OnExamined); + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMarkingsSet); + SubscribeLocalEvent(OnBaseLayersSet); + SubscribeLocalEvent>(OnVerbsRequest); + SubscribeLocalEvent(OnExamined); } - private void Synchronize(EntityUid uid, HumanoidComponent? component = null) - { - 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) + private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args) { if (string.IsNullOrEmpty(humanoid.Species)) { @@ -67,7 +49,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem 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 species = GetSpeciesRepresentation(component.Species).ToLower(); @@ -82,7 +64,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// The mob's entity UID. /// The character profile to load. /// Humanoid component of the entity - 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)) { @@ -91,11 +73,11 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem SetSpecies(uid, profile.Species, false, humanoid); humanoid.Sex = profile.Sex; + humanoid.EyeColor = profile.Appearance.EyeColor; 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. @@ -117,7 +99,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem humanoid.Age = profile.Age; - Synchronize(uid); + Dirty(humanoid); } // this was done enough times that it only made sense to do it here @@ -129,8 +111,8 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// Target entity to apply the source entity's appearance to. /// Source entity's humanoid component. /// Target entity's humanoid component. - public void CloneAppearance(EntityUid source, EntityUid target, HumanoidComponent? sourceHumanoid = null, - HumanoidComponent? targetHumanoid = null) + public void CloneAppearance(EntityUid source, EntityUid target, HumanoidAppearanceComponent? sourceHumanoid = null, + HumanoidAppearanceComponent? targetHumanoid = null) { if (!Resolve(source, ref sourceHumanoid) || !Resolve(target, ref targetHumanoid)) { @@ -141,7 +123,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem targetHumanoid.SkinColor = sourceHumanoid.SkinColor; targetHumanoid.Sex = sourceHumanoid.Sex; targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers); - targetHumanoid.CurrentMarkings = new(sourceHumanoid.CurrentMarkings); + targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet); targetHumanoid.Gender = sourceHumanoid.Gender; if (TryComp(target, out var grammar)) @@ -149,180 +131,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem grammar.Gender = sourceHumanoid.Gender; } - Synchronize(target, targetHumanoid); - } - - /// - /// 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. - /// - /// The humanoid mob's UID. - /// The species to set the mob to. Will return if the species prototype was invalid. - /// Whether to immediately synchronize this to the humanoid mob, or not. - /// Humanoid component of the entity - public void SetSpecies(EntityUid uid, string species, bool sync = true, HumanoidComponent? humanoid = null) - { - if (!Resolve(uid, ref humanoid) || !_prototypeManager.TryIndex(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); - } - } - - /// - /// Sets the skin color of this humanoid mob. Will only affect base layers that are not custom, - /// custom base layers should use instead. - /// - /// The humanoid mob's UID. - /// Skin color to set on the humanoid mob. - /// Whether to synchronize this to the humanoid mob, or not. - /// Humanoid component of the entity - 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); - } - - /// - /// 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. - /// - /// The humanoid mob's UID. - /// The layer to target on this humanoid mob. - /// The ID of the sprite to use. See . - /// Whether to synchronize this to the humanoid mob, or not. - /// Humanoid component of the entity - public void SetBaseLayerId(EntityUid uid, HumanoidVisualLayers layer, string id, bool sync = true, - HumanoidComponent? humanoid = null) - { - if (!Resolve(uid, ref humanoid) - || !_prototypeManager.HasIndex(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); - } - - /// - /// Sets the color of this humanoid mob's base layer. See for a - /// description of how base layers work. - /// - /// The humanoid mob's UID. - /// The layer to target on this humanoid mob. - /// The color to set this base layer to. - 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); - } - - /// - /// Toggles a humanoid's sprite layer visibility. - /// - /// Humanoid mob's UID - /// Layer to toggle visibility for - /// Humanoid component of the entity - 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); - } - - /// - /// Sets the visibility for multiple layers at once on a humanoid's sprite. - /// - /// Humanoid mob's UID - /// An enumerable of all sprite layers that are going to have their visibility set - /// The visibility state of the layers given - /// If this is a permanent change, or temporary. Permanent layers are stored in their own hash set. - /// Humanoid component of the entity - public void SetLayersVisibility(EntityUid uid, IEnumerable 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); + Dirty(targetHumanoid); } /// @@ -334,7 +143,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// Whether to immediately sync this marking or not /// If this marking was forced (ignores marking points) /// Humanoid component of the entity - 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) || !_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) - Synchronize(uid, humanoid); + Dirty(humanoid); } /// @@ -367,7 +176,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// Whether to immediately sync this marking or not /// If this marking was forced (ignores marking points) /// Humanoid component of the entity - public void AddMarking(EntityUid uid, string marking, IReadOnlyList colors, bool sync = true, bool forced = false, HumanoidComponent? humanoid = null) + public void AddMarking(EntityUid uid, string marking, IReadOnlyList colors, bool sync = true, bool forced = false, HumanoidAppearanceComponent? humanoid = null) { if (!Resolve(uid, ref humanoid) || !_markingManager.Markings.TryGetValue(marking, out var prototype)) @@ -377,10 +186,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem var markingObject = new Marking(marking, colors); markingObject.Forced = forced; - humanoid.CurrentMarkings.AddBack(prototype.MarkingCategory, markingObject); + humanoid.MarkingSet.AddBack(prototype.MarkingCategory, markingObject); if (sync) - Synchronize(uid, humanoid); + Dirty(humanoid); } /// @@ -390,7 +199,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// The marking to try and remove. /// Whether to immediately sync this to the humanoid /// Humanoid component of the entity - 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) || !_markingManager.Markings.TryGetValue(marking, out var prototype)) @@ -398,10 +207,10 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem return; } - humanoid.CurrentMarkings.Remove(prototype.MarkingCategory, marking); + humanoid.MarkingSet.Remove(prototype.MarkingCategory, marking); if (sync) - Synchronize(uid, humanoid); + Dirty(humanoid); } /// @@ -411,19 +220,18 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// Category of the marking /// Index of the marking /// Humanoid component of the entity - 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 || !Resolve(uid, ref humanoid) - || !humanoid.CurrentMarkings.TryGetCategory(category, out var markings) + || !humanoid.MarkingSet.TryGetCategory(category, out var markings) || index >= markings.Count) { return; } - humanoid.CurrentMarkings.Remove(category, index); - - Synchronize(uid, humanoid); + humanoid.MarkingSet.Remove(category, index); + Dirty(humanoid); } /// @@ -434,12 +242,12 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// Index of the marking /// The marking ID to use /// Humanoid component of the entity - 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 || !_markingManager.MarkingsByCategory(category).TryGetValue(markingId, out var markingPrototype) || !Resolve(uid, ref humanoid) - || !humanoid.CurrentMarkings.TryGetCategory(category, out var markings) + || !humanoid.MarkingSet.TryGetCategory(category, out var markings) || index >= markings.Count) { return; @@ -451,9 +259,8 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem marking.SetColor(i, markings[index].MarkingColors[i]); } - humanoid.CurrentMarkings.Replace(category, index, marking); - - Synchronize(uid, humanoid); + humanoid.MarkingSet.Replace(category, index, marking); + Dirty(humanoid); } /// @@ -465,11 +272,11 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem /// The marking colors to use /// Humanoid component of the entity public void SetMarkingColor(EntityUid uid, MarkingCategories category, int index, List colors, - HumanoidComponent? humanoid = null) + HumanoidAppearanceComponent? humanoid = null) { if (index < 0 || !Resolve(uid, ref humanoid) - || !humanoid.CurrentMarkings.TryGetCategory(category, out var markings) + || !humanoid.MarkingSet.TryGetCategory(category, out var markings) || index >= markings.Count) { return; @@ -480,7 +287,7 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem markings[index].SetColor(i, colors[i]); } - Synchronize(uid, humanoid); + Dirty(humanoid); } /// @@ -521,13 +328,13 @@ public sealed partial class HumanoidSystem : SharedHumanoidSystem 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)) { return; } - humanoid.CurrentMarkings.EnsureDefault(humanoid.SkinColor, _markingManager); + humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, _markingManager); } } diff --git a/Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs b/Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs index d788a53cc4..4b109c5336 100644 --- a/Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs +++ b/Content.Server/Humanoid/Systems/HumanoidSystem.Modifier.cs @@ -7,12 +7,12 @@ using Robust.Server.Player; namespace Content.Server.Humanoid; -public sealed partial class HumanoidSystem +public sealed partial class HumanoidAppearanceSystem { [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - private void OnVerbsRequest(EntityUid uid, HumanoidComponent component, GetVerbsEvent args) + private void OnVerbsRequest(EntityUid uid, HumanoidAppearanceComponent component, GetVerbsEvent args) { if (!TryComp(args.User, out var actor)) { @@ -35,12 +35,12 @@ public sealed partial class HumanoidSystem _uiSystem.TrySetUiState( uid, 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) { if (message.Session is not IPlayerSession player @@ -55,24 +55,21 @@ public sealed partial class HumanoidSystem } else { - if (!component.CustomBaseLayers.TryAdd(message.Layer, message.Info)) - { - component.CustomBaseLayers[message.Layer] = message.Info; - } + component.CustomBaseLayers[message.Layer] = message.Info.Value; } - Synchronize(uid, component); + Dirty(component); if (message.ResendState) { _uiSystem.TrySetUiState( uid, 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) { if (message.Session is not IPlayerSession player @@ -81,15 +78,15 @@ public sealed partial class HumanoidSystem return; } - component.CurrentMarkings = message.MarkingSet; - Synchronize(uid, component); + component.MarkingSet = message.MarkingSet; + Dirty(component); if (message.ResendState) { _uiSystem.TrySetUiState( uid, HumanoidMarkingModifierKey.Key, - new HumanoidMarkingModifierState(component.CurrentMarkings, component.Species, component.SkinColor, component.CustomBaseLayers)); + new HumanoidMarkingModifierState(component.MarkingSet, component.Species, component.SkinColor, component.CustomBaseLayers)); } } diff --git a/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs b/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs index b55c025beb..0775f315c9 100644 --- a/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs +++ b/Content.Server/Humanoid/Systems/RandomHumanoidAppearanceSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.CharacterAppearance.Components; +using Content.Server.CharacterAppearance.Components; using Content.Shared.Humanoid; using Content.Shared.Preferences; @@ -6,7 +6,7 @@ namespace Content.Server.Humanoid.Systems; public sealed class RandomHumanoidAppearanceSystem : EntitySystem { - [Dependency] private readonly HumanoidSystem _humanoid = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; public override void Initialize() { @@ -18,7 +18,7 @@ public sealed class RandomHumanoidAppearanceSystem : EntitySystem private void OnMapInit(EntityUid uid, RandomHumanoidAppearanceComponent component, MapInitEvent args) { // 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; } diff --git a/Content.Server/Humanoid/Systems/RandomHumanoidSystem.cs b/Content.Server/Humanoid/Systems/RandomHumanoidSystem.cs index 5a109b2cc9..ccbc9d50c1 100644 --- a/Content.Server/Humanoid/Systems/RandomHumanoidSystem.cs +++ b/Content.Server/Humanoid/Systems/RandomHumanoidSystem.cs @@ -17,7 +17,7 @@ public sealed class RandomHumanoidSystem : EntitySystem [Dependency] private readonly IComponentFactory _compFactory = default!; [Dependency] private readonly ISerializationManager _serialization = default!; - [Dependency] private readonly HumanoidSystem _humanoid = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; /// public override void Initialize() diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index a7dee24b23..cf5d2e181e 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -118,7 +118,7 @@ public class IdentitySystem : SharedIdentitySystem /// private IdentityRepresentation GetIdentityRepresentation(EntityUid target, InventoryComponent? inventory=null, - HumanoidComponent? appearance=null) + HumanoidAppearanceComponent? appearance=null) { int age = 18; Gender gender = Gender.Epicene; diff --git a/Content.Server/MagicMirror/MagicMirrorSystem.cs b/Content.Server/MagicMirror/MagicMirrorSystem.cs index 49135a3255..c242b0b936 100644 --- a/Content.Server/MagicMirror/MagicMirrorSystem.cs +++ b/Content.Server/MagicMirror/MagicMirrorSystem.cs @@ -13,7 +13,7 @@ namespace Content.Server.MagicMirror; public sealed class MagicMirrorSystem : EntitySystem { [Dependency] private readonly MarkingManager _markings = default!; - [Dependency] private readonly HumanoidSystem _humanoid = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = 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) { - if (!HasComp(args.User)) + if (!HasComp(args.User)) args.Cancel(); } private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, MagicMirrorSelectMessage message) { - if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) + if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) { return; } @@ -63,7 +63,7 @@ public sealed class MagicMirrorSystem : EntitySystem private void OnMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent component, MagicMirrorChangeColorMessage message) { - if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) + if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) { return; } @@ -90,7 +90,7 @@ public sealed class MagicMirrorSystem : EntitySystem private void OnMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent component, MagicMirrorRemoveSlotMessage message) { - if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) + if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) { return; } @@ -116,7 +116,7 @@ public sealed class MagicMirrorSystem : EntitySystem private void OnMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent component, MagicMirrorAddSlotMessage message) { - if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) + if (message.Session.AttachedEntity == null || !TryComp(message.Session.AttachedEntity.Value, out var humanoid)) { return; } @@ -145,34 +145,34 @@ public sealed class MagicMirrorSystem : EntitySystem 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) { return; } - var hair = humanoid.CurrentMarkings.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) + var hair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.Hair, out var hairMarkings) ? new List(hairMarkings) : new(); - var facialHair = humanoid.CurrentMarkings.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings) + var facialHair = humanoid.MarkingSet.TryGetCategory(MarkingCategories.FacialHair, out var facialHairMarkings) ? new List(facialHairMarkings) : new(); var msg = new MagicMirrorUiData( humanoid.Species, hair, - humanoid.CurrentMarkings.PointsLeft(MarkingCategories.Hair) + hair.Count, + humanoid.MarkingSet.PointsLeft(MarkingCategories.Hair) + hair.Count, facialHair, - humanoid.CurrentMarkings.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count); + humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count); _uiSystem.TrySendUiMessage(uid, MagicMirrorUiKey.Key, msg, player); } private void AfterUIOpen(EntityUid uid, MagicMirrorComponent component, AfterActivatableUIOpenEvent args) { - var looks = Comp(args.User); + var looks = Comp(args.User); var actor = Comp(args.User); UpdateInterface(uid, args.User, args.Session); diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index 375a236a88..9ec1b3a290 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -26,6 +26,7 @@ using Robust.Shared.Random; using Robust.Shared.Configuration; using Robust.Server.Player; using Robust.Shared.Physics.Components; +using Content.Shared.Humanoid; namespace Content.Server.Medical.BiomassReclaimer { @@ -256,7 +257,7 @@ namespace Content.Server.Medical.BiomassReclaimer // Reject souled bodies in easy mode. if (_configManager.GetCVar(CCVars.BiomassEasyMode) && - HasComp(dragged) && + HasComp(dragged) && TryComp(dragged, out var mindComp)) { if (mindComp.Mind?.UserId != null && _playerManager.TryGetSessionById(mindComp.Mind.UserId.Value, out _)) diff --git a/Content.Server/Polymorph/Systems/PolymorphableSystem.cs b/Content.Server/Polymorph/Systems/PolymorphableSystem.cs index cf96997ca4..27300eb0ec 100644 --- a/Content.Server/Polymorph/Systems/PolymorphableSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphableSystem.cs @@ -32,7 +32,7 @@ namespace Content.Server.Polymorph.Systems [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = 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!; public override void Initialize() diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index f9f9d6a3e0..ea04d00e82 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -276,7 +276,7 @@ namespace Content.Server.Preferences.Managers case HumanoidCharacterProfile hp: { var prototypeManager = IoCManager.Resolve(); - var selectedSpecies = HumanoidSystem.DefaultSpecies; + var selectedSpecies = HumanoidAppearanceSystem.DefaultSpecies; if (prototypeManager.TryIndex(hp.Species, out var species) && species.RoundStart) { diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index 66aa1a0151..b26692144d 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -29,6 +29,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Revenant.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; +using Content.Shared.Humanoid; namespace Content.Server.Revenant.EntitySystems; @@ -70,7 +71,7 @@ public sealed partial class RevenantSystem return; } - if (!HasComp(target) || !HasComp(target) || HasComp(target)) + if (!HasComp(target) || !HasComp(target) || HasComp(target)) return; args.Handled = true; diff --git a/Content.Server/Speech/VocalSystem.cs b/Content.Server/Speech/VocalSystem.cs index 9c08518e8d..718bad6702 100644 --- a/Content.Server/Speech/VocalSystem.cs +++ b/Content.Server/Speech/VocalSystem.cs @@ -70,7 +70,7 @@ public sealed class VocalSystem : EntitySystem if (!_blocker.CanSpeak(uid)) return false; - var sex = CompOrNull(uid)?.Sex ?? Sex.Unsexed; + var sex = CompOrNull(uid)?.Sex ?? Sex.Unsexed; if (_random.Prob(component.WilhelmProbability)) { diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index b41b00b88c..3c13bce8a6 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -34,7 +34,7 @@ public sealed class StationSpawningSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = 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 InventorySystem _inventorySystem = default!; [Dependency] private readonly PDASystem _pdaSystem = default!; @@ -106,7 +106,7 @@ public sealed class StationSpawningSystem : EntitySystem } var entity = EntityManager.SpawnEntity( - _prototypeManager.Index(profile?.Species ?? HumanoidSystem.DefaultSpecies).Prototype, + _prototypeManager.Index(profile?.Species ?? HumanoidAppearanceSystem.DefaultSpecies).Prototype, coordinates); if (job?.StartingGear != null) diff --git a/Content.Server/Store/Conditions/BuyerSpeciesCondition.cs b/Content.Server/Store/Conditions/BuyerSpeciesCondition.cs index e99e8f52da..404c9422a0 100644 --- a/Content.Server/Store/Conditions/BuyerSpeciesCondition.cs +++ b/Content.Server/Store/Conditions/BuyerSpeciesCondition.cs @@ -3,6 +3,7 @@ using Content.Shared.Humanoid; using Content.Shared.Store; using Content.Shared.Humanoid.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Content.Shared.Humanoid; namespace Content.Server.Store.Conditions; @@ -28,7 +29,7 @@ public sealed class BuyerSpeciesCondition : ListingCondition { var ent = args.EntityManager; - if (!ent.TryGetComponent(args.Buyer, out var appearance)) + if (!ent.TryGetComponent(args.Buyer, out var appearance)) return true; // inanimate or non-humanoid entities should be handled elsewhere, main example being surplus crates if (Blacklist != null) diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index e9ffc851c9..863d02a8aa 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -34,7 +34,7 @@ namespace Content.Server.Zombies [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly HumanoidSystem _humanoidSystem = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; public override void Initialize() { diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index aad882dab5..d2a8f6f71b 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -46,7 +46,7 @@ namespace Content.Server.Zombies [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly ServerInventorySystem _serverInventory = 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 MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly IChatManager _chatMan = default!; @@ -125,7 +125,7 @@ namespace Content.Server.Zombies Dirty(melee); //We have specific stuff for humanoid zombies because they matter more - if (TryComp(target, out var huApComp)) //huapcomp + if (TryComp(target, out var huApComp)) //huapcomp { //store some values before changing them in case the humanoid get cloned later zombiecomp.BeforeZombifiedSkinColor = huApComp.SkinColor; diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index 194971694b..e8b694602c 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -1,7 +1,9 @@ using Content.Shared.Clothing.Components; +using Content.Shared.Humanoid; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; +using Content.Shared.Tag; using Robust.Shared.GameStates; namespace Content.Shared.Clothing.EntitySystems; @@ -9,6 +11,8 @@ namespace Content.Shared.Clothing.EntitySystems; public abstract class ClothingSystem : EntitySystem { [Dependency] private readonly SharedItemSystem _itemSys = default!; + [Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidSystem = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; public override void Initialize() { @@ -23,11 +27,15 @@ public abstract class ClothingSystem : EntitySystem protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args) { 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) { 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) diff --git a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs new file mode 100644 index 0000000000..180475bcef --- /dev/null +++ b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs @@ -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 BaseLayers = new(); + + [DataField("permanentlyHidden")] + public HashSet PermanentlyHidden = new(); + + // Couldn't these be somewhere else? + + [DataField("gender")] + [ViewVariables] public Gender Gender = default!; + + [DataField("age")] + [ViewVariables] public int Age = 18; + + /// + /// 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. + /// + [DataField("customBaseLayers")] + public Dictionary CustomBaseLayers = new(); + + /// + /// Current species. Dictates things like base body sprites, + /// base humanoid to spawn, etc. + /// + [DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Species { get; set; } = string.Empty; + + /// + /// The initial profile and base layers to apply to this humanoid. + /// + [DataField("initial", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? Initial { get; } + + /// + /// Skin color of this humanoid. + /// + [DataField("skinColor")] + public Color SkinColor { get; set; } = Color.FromHex("#C0967F"); + + /// + /// Visual layers currently hidden. This will affect the base sprite + /// on this humanoid layer, and any markings that sit above it. + /// + [DataField("hiddenLayers")] + public HashSet 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 PermanentlyHidden; + public readonly HashSet HiddenLayers; + public readonly Dictionary 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 permanentlyHidden, + HashSet hiddenLayers, + Dictionary 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().HasIndex(id)); + ID = id; + Color = color; + } + + /// + /// ID of this custom base layer. Must be a . + /// + [DataField("id", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] + public string ID { init; get; } + + /// + /// Color of this custom base layer. Null implies skin colour. + /// + [DataField("color")] + public Color? Color { init; get; } + } +} diff --git a/Content.Shared/Humanoid/HumanoidComponent.cs b/Content.Shared/Humanoid/HumanoidComponent.cs deleted file mode 100644 index e85a5ba2fe..0000000000 --- a/Content.Shared/Humanoid/HumanoidComponent.cs +++ /dev/null @@ -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 -{ - /// - /// Current species. Dictates things like base body sprites, - /// base humanoid to spawn, etc. - /// - [DataField("species", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Species { get; set; } = string.Empty; - - /// - /// The initial profile and base layers to apply to this humanoid. - /// - [DataField("initial", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Initial { get; } - - /// - /// Skin color of this humanoid. - /// - [DataField("skinColor")] - public Color SkinColor { get; set; } = Color.FromHex("#C0967F"); - - /// - /// Visual layers currently hidden. This will affect the base sprite - /// on this humanoid layer, and any markings that sit above it. - /// - [ViewVariables] public readonly HashSet HiddenLayers = new(); - - [DataField("sex")] public Sex Sex = Sex.Male; - - public MarkingSet CurrentMarkings = new(); - - /// - /// 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. - /// - [ViewVariables(VVAccess.ReadOnly)] - public Dictionary CustomBaseLayers = new(); - - public HashSet PermanentlyHidden = new(); - - public HashSet AllHiddenLayers - { - get - { - var result = new HashSet(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 CurrentClientMarkings = new(); - - public Dictionary BaseLayers = new(); - - public string LastSpecies = default!; -} - -[DataDefinition] -[Serializable, NetSerializable] -public sealed class CustomBaseLayerInfo -{ - public CustomBaseLayerInfo(string id, Color color) - { - ID = id; - Color = color; - } - - /// - /// ID of this custom base layer. Must be a . - /// - [DataField("id")] - public string ID { get; } - - /// - /// Color of this custom base layer. - /// - [DataField("color")] - public Color Color { get; } -} diff --git a/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs b/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs index cd29914585..0f8b940bd6 100644 --- a/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs +++ b/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs @@ -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}"; + } + /// /// 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. diff --git a/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs b/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs deleted file mode 100644 index ecfd5d479c..0000000000 --- a/Content.Shared/Humanoid/HumanoidVisualizerKeys.cs +++ /dev/null @@ -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 customBaseLayerInfo, Color skinColor, Sex sex, List layerVisibility, List markings) - { - Species = species; - CustomBaseLayerInfo = customBaseLayerInfo; - SkinColor = skinColor; - Sex = sex; - LayerVisibility = layerVisibility; - Markings = markings; - } - - public string Species { get; } - public Dictionary CustomBaseLayerInfo { get; } - public Color SkinColor { get; } - public Sex Sex { get; } - public List LayerVisibility { get; } - public List Markings { get; } - - public object Clone() - { - return new HumanoidVisualizerData(Species, new(CustomBaseLayerInfo), SkinColor, Sex, new(LayerVisibility), new(Markings)); - } -} diff --git a/Content.Shared/Humanoid/Markings/Marking.cs b/Content.Shared/Humanoid/Markings/Marking.cs index 970ca9c8e3..25413da91e 100644 --- a/Content.Shared/Humanoid/Markings/Marking.cs +++ b/Content.Shared/Humanoid/Markings/Marking.cs @@ -3,6 +3,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.Humanoid.Markings { + [DataDefinition] [Serializable, NetSerializable] public sealed class Marking : IEquatable, IComparable, IComparable { @@ -54,7 +55,7 @@ namespace Content.Shared.Humanoid.Markings /// /// If this marking is currently visible. /// - [ViewVariables] + [DataField("visible")] public bool Visible = true; /// diff --git a/Content.Shared/Humanoid/Markings/MarkingsSet.cs b/Content.Shared/Humanoid/Markings/MarkingsSet.cs index 807cea672d..4a6a5163ff 100644 --- a/Content.Shared/Humanoid/Markings/MarkingsSet.cs +++ b/Content.Shared/Humanoid/Markings/MarkingsSet.cs @@ -24,6 +24,7 @@ namespace Content.Shared.Humanoid.Markings; /// This is serializable for the admin panel that sets markings on demand for a player. /// Most APIs that accept a set of markings usually use a List of type Marking instead. /// +[DataDefinition] [Serializable, NetSerializable] public sealed class MarkingSet { @@ -40,16 +41,14 @@ public sealed class MarkingSet /// feature of markings, which is the limit of markings you can put on a /// humanoid. /// - private Dictionary> _markings = new(); - - // why i didn't encapsulate this in the first place, i won't know + [DataField("markings")] + public Dictionary> Markings = new(); /// /// Marking points for each category. /// - private Dictionary _points = new(); - - public IReadOnlyList this[MarkingCategories category] => _markings[category]; + [DataField("points")] + public Dictionary Points = new(); public MarkingSet() {} @@ -71,7 +70,7 @@ public sealed class MarkingSet return; } - _points = MarkingPoints.CloneMarkingPointDictionary(points.Points); + Points = MarkingPoints.CloneMarkingPointDictionary(points.Points); foreach (var marking in markings) { @@ -111,7 +110,7 @@ public sealed class MarkingSet /// The other marking set. public MarkingSet(MarkingSet other) { - foreach (var (key, list) in other._markings) + foreach (var (key, list) in other.Markings) { foreach (var marking in list) { @@ -119,7 +118,7 @@ public sealed class MarkingSet } } - _points = MarkingPoints.CloneMarkingPointDictionary(other._points); + Points = MarkingPoints.CloneMarkingPointDictionary(other.Points); } /// @@ -137,7 +136,7 @@ public sealed class MarkingSet var speciesProto = prototypeManager.Index(species); var onlyWhitelisted = prototypeManager.Index(speciesProto.MarkingPoints).OnlyWhitelisted; - foreach (var (category, list) in _markings) + foreach (var (category, list) in Markings) { foreach (var marking in list) { @@ -175,7 +174,7 @@ public sealed class MarkingSet IoCManager.Resolve(ref markingManager); var toRemove = new List(); - foreach (var (category, list) in _markings) + foreach (var (category, list) in Markings) { for (var i = 0; i < list.Count; i++) { @@ -207,7 +206,7 @@ public sealed class MarkingSet { IoCManager.Resolve(ref markingManager); - foreach (var (category, points) in _points) + foreach (var (category, points) in Points) { if (points.Points <= 0 || points.DefaultMarkings.Count <= 0) { @@ -251,7 +250,7 @@ public sealed class MarkingSet /// A number equal or greater than zero if the category exists, -1 otherwise. public int PointsLeft(MarkingCategories category) { - if (!_points.TryGetValue(category, out var points)) + if (!Points.TryGetValue(category, out var points)) { return -1; } @@ -266,7 +265,7 @@ public sealed class MarkingSet /// The marking instance in question. public void AddFront(MarkingCategories category, Marking marking) { - if (!marking.Forced && _points.TryGetValue(category, out var points)) + if (!marking.Forced && Points.TryGetValue(category, out var points)) { if (points.Points <= 0) { @@ -276,10 +275,10 @@ public sealed class MarkingSet points.Points--; } - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { markings = new(); - _markings[category] = markings; + Markings[category] = markings; } markings.Insert(0, marking); @@ -292,7 +291,7 @@ public sealed class MarkingSet /// public void AddBack(MarkingCategories category, Marking marking) { - if (!marking.Forced && _points.TryGetValue(category, out var points)) + if (!marking.Forced && Points.TryGetValue(category, out var points)) { if (points.Points <= 0) { @@ -302,10 +301,10 @@ public sealed class MarkingSet points.Points--; } - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { markings = new(); - _markings[category] = markings; + Markings[category] = markings; } @@ -320,7 +319,7 @@ public sealed class MarkingSet public List AddCategory(MarkingCategories category) { var markings = new List(); - _markings.Add(category, markings); + Markings.Add(category, markings); return markings; } @@ -332,7 +331,7 @@ public sealed class MarkingSet /// The marking to insert. public void Replace(MarkingCategories category, int index, Marking marking) { - if (index < 0 || !_markings.TryGetValue(category, out var markings) + if (index < 0 || !Markings.TryGetValue(category, out var markings) || index >= markings.Count) { return; @@ -349,7 +348,7 @@ public sealed class MarkingSet /// True if removed, false otherwise. public bool Remove(MarkingCategories category, string id) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return false; } @@ -361,7 +360,7 @@ public sealed class MarkingSet continue; } - if (!markings[i].Forced && _points.TryGetValue(category, out var points)) + if (!markings[i].Forced && Points.TryGetValue(category, out var points)) { points.Points++; } @@ -381,7 +380,7 @@ public sealed class MarkingSet /// True if removed, false otherwise. public void Remove(MarkingCategories category, int idx) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return; } @@ -391,7 +390,7 @@ public sealed class MarkingSet return; } - if (!markings[idx].Forced && _points.TryGetValue(category, out var points)) + if (!markings[idx].Forced && Points.TryGetValue(category, out var points)) { points.Points++; } @@ -406,12 +405,12 @@ public sealed class MarkingSet /// True if removed, false otherwise. public bool RemoveCategory(MarkingCategories category) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return false; } - if (_points.TryGetValue(category, out var points)) + if (Points.TryGetValue(category, out var points)) { foreach (var marking in markings) { @@ -424,7 +423,7 @@ public sealed class MarkingSet } } - _markings.Remove(category); + Markings.Remove(category); return true; } @@ -447,7 +446,7 @@ public sealed class MarkingSet /// The index of the marking, otherwise a negative number. public int FindIndexOf(MarkingCategories category, string id) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return -1; } @@ -465,7 +464,7 @@ public sealed class MarkingSet { markings = null; - if (_markings.TryGetValue(category, out var list)) + if (Markings.TryGetValue(category, out var list)) { markings = list; return true; @@ -485,7 +484,7 @@ public sealed class MarkingSet { marking = null; - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return false; } @@ -509,7 +508,7 @@ public sealed class MarkingSet /// Index of the marking. public void ShiftRankUp(MarkingCategories category, int idx) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return; } @@ -529,7 +528,7 @@ public sealed class MarkingSet /// Index of the marking from the end public void ShiftRankUpFromEnd(MarkingCategories category, int idx) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return; } @@ -544,7 +543,7 @@ public sealed class MarkingSet /// Index of the marking. public void ShiftRankDown(MarkingCategories category, int idx) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return; } @@ -564,7 +563,7 @@ public sealed class MarkingSet /// Index of the marking from the end public void ShiftRankDownFromEnd(MarkingCategories category, int idx) { - if (!_markings.TryGetValue(category, out var markings)) + if (!Markings.TryGetValue(category, out var markings)) { return; } @@ -579,7 +578,7 @@ public sealed class MarkingSet public ForwardMarkingEnumerator GetForwardEnumerator() { var markings = new List(); - foreach (var (_, list) in _markings) + foreach (var (_, list) in Markings) { markings.AddRange(list); } @@ -595,7 +594,7 @@ public sealed class MarkingSet public ForwardMarkingEnumerator GetForwardEnumerator(MarkingCategories category) { var markings = new List(); - if (_markings.TryGetValue(category, out var listing)) + if (Markings.TryGetValue(category, out var listing)) { markings = new(listing); } @@ -610,7 +609,7 @@ public sealed class MarkingSet public ReverseMarkingEnumerator GetReverseEnumerator() { var markings = new List(); - foreach (var (_, list) in _markings) + foreach (var (_, list) in Markings) { markings.AddRange(list); } @@ -626,7 +625,7 @@ public sealed class MarkingSet public ReverseMarkingEnumerator GetReverseEnumerator(MarkingCategories category) { var markings = new List(); - if (_markings.TryGetValue(category, out var listing)) + if (Markings.TryGetValue(category, out var listing)) { markings = new(listing); } @@ -636,8 +635,8 @@ public sealed class MarkingSet public bool CategoryEquals(MarkingCategories category, MarkingSet other) { - if (!_markings.TryGetValue(category, out var markings) - || !other._markings.TryGetValue(category, out var markingsOther)) + if (!Markings.TryGetValue(category, out var markings) + || !other.Markings.TryGetValue(category, out var markingsOther)) { return false; } @@ -647,7 +646,7 @@ public sealed class MarkingSet public bool Equals(MarkingSet other) { - foreach (var (category, _) in _markings) + foreach (var (category, _) in Markings) { if (!CategoryEquals(category, other)) { @@ -665,7 +664,7 @@ public sealed class MarkingSet /// Enumerator of marking categories that were different between the two. public IEnumerable CategoryDifference(MarkingSet other) { - foreach (var (category, _) in _markings) + foreach (var (category, _) in Markings) { if (!CategoryEquals(category, other)) { diff --git a/Content.Shared/Humanoid/Prototypes/HumanoidProfilePrototype.cs b/Content.Shared/Humanoid/Prototypes/HumanoidProfilePrototype.cs index 12c49dfa9d..f2177db9f1 100644 --- a/Content.Shared/Humanoid/Prototypes/HumanoidProfilePrototype.cs +++ b/Content.Shared/Humanoid/Prototypes/HumanoidProfilePrototype.cs @@ -1,5 +1,6 @@ using Content.Shared.Preferences; using Robust.Shared.Prototypes; +using static Content.Shared.Humanoid.HumanoidAppearanceState; namespace Content.Shared.Humanoid.Prototypes; diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs new file mode 100644 index 0000000000..7feb0da53d --- /dev/null +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -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; + +/// +/// 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. +/// +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(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); + } + + /// + /// Toggles a humanoid's sprite layer visibility. + /// + /// Humanoid mob's UID + /// Layer to toggle visibility for + /// Humanoid component of the entity + 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); + } + + /// + /// Sets the visibility for multiple layers at once on a humanoid's sprite. + /// + /// Humanoid mob's UID + /// An enumerable of all sprite layers that are going to have their visibility set + /// The visibility state of the layers given + /// If this is a permanent change, or temporary. Permanent layers are stored in their own hash set. + /// Humanoid component of the entity + public void SetLayersVisibility(EntityUid uid, IEnumerable 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); + } + } + + /// + /// 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. + /// + /// The humanoid mob's UID. + /// The species to set the mob to. Will return if the species prototype was invalid. + /// Whether to immediately synchronize this to the humanoid mob, or not. + /// Humanoid component of the entity + public void SetSpecies(EntityUid uid, string species, bool sync = true, HumanoidAppearanceComponent? humanoid = null) + { + if (!Resolve(uid, ref humanoid) || !_prototypeManager.TryIndex(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); + } + + /// + /// Sets the skin color of this humanoid mob. Will only affect base layers that are not custom, + /// custom base layers should use instead. + /// + /// The humanoid mob's UID. + /// Skin color to set on the humanoid mob. + /// Whether to synchronize this to the humanoid mob, or not. + /// Humanoid component of the entity + 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); + } + + /// + /// 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. + /// + /// The humanoid mob's UID. + /// The layer to target on this humanoid mob. + /// The ID of the sprite to use. See . + /// Whether to synchronize this to the humanoid mob, or not. + /// Humanoid component of the entity + 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); + } + + /// + /// Sets the color of this humanoid mob's base layer. See for a + /// description of how base layers work. + /// + /// The humanoid mob's UID. + /// The layer to target on this humanoid mob. + /// The color to set this base layer to. + 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); + } +} diff --git a/Content.Shared/Humanoid/SharedHumanoidMarkingModifierSystem.cs b/Content.Shared/Humanoid/SharedHumanoidMarkingModifierSystem.cs index 193d79e5a8..9f8c7d6712 100644 --- a/Content.Shared/Humanoid/SharedHumanoidMarkingModifierSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidMarkingModifierSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Humanoid.Markings; using Robust.Shared.Serialization; +using static Content.Shared.Humanoid.HumanoidAppearanceState; namespace Content.Shared.Humanoid; @@ -40,6 +41,7 @@ public sealed class HumanoidMarkingModifierBaseLayersSetMessage : BoundUserInter [Serializable, NetSerializable] public sealed class HumanoidMarkingModifierState : BoundUserInterfaceState { + // TODO just use the component state, remove the BUI state altogether. public HumanoidMarkingModifierState(MarkingSet markingSet, string species, Color skinColor, Dictionary customBaseLayers) { MarkingSet = markingSet; diff --git a/Content.Shared/Humanoid/SharedHumanoidSystem.cs b/Content.Shared/Humanoid/SharedHumanoidSystem.cs deleted file mode 100644 index 0bdcf19761..0000000000 --- a/Content.Shared/Humanoid/SharedHumanoidSystem.cs +++ /dev/null @@ -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; - -/// -/// 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. -/// -public abstract class SharedHumanoidSystem : EntitySystem -{ - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - - public const string DefaultSpecies = "Human"; - - public void SetAppearance(EntityUid uid, - string species, - Dictionary customBaseLayer, - Color skinColor, - Sex sex, - List visLayers, - List 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); - } -} diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index a7867e06c0..6057588d3c 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -99,7 +99,7 @@ namespace Content.Shared.Preferences /// /// Get the default humanoid character profile, using internal constant values. - /// Defaults to for the species. + /// Defaults to for the species. /// /// public static HumanoidCharacterProfile Default() @@ -107,7 +107,7 @@ namespace Content.Shared.Preferences return new( "John Doe", "", - SharedHumanoidSystem.DefaultSpecies, + SharedHumanoidAppearanceSystem.DefaultSpecies, 18, Sex.Male, Gender.Male, @@ -126,9 +126,9 @@ namespace Content.Shared.Preferences /// /// Return a default character profile, based on species. /// - /// The species to use in this default profile. The default species is . + /// The species to use in this default profile. The default species is . /// Humanoid character profile with default settings. - public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies) + public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) { return new( "John Doe", @@ -164,7 +164,7 @@ namespace Content.Shared.Preferences return RandomWithSpecies(species); } - public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidSystem.DefaultSpecies) + public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) { var prototypeManager = IoCManager.Resolve(); var random = IoCManager.Resolve(); diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs index 3f6a1804b3..25b2af24f2 100644 --- a/Content.Shared/Zombies/ZombieComponent.cs +++ b/Content.Shared/Zombies/ZombieComponent.cs @@ -3,6 +3,7 @@ using Content.Shared.Humanoid; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using static Content.Shared.Humanoid.HumanoidAppearanceState; namespace Content.Shared.Zombies { diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index b844cbba18..41377657d9 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -32,7 +32,7 @@ - MobMask layer: - MachineLayer - - type: Humanoid + - type: HumanoidAppearance - type: AnimationPlayer - type: MeleeWeapon hidden: true diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 9fecc97a77..40fe6e3d5b 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -173,7 +173,7 @@ heatDamage: types: Heat: 0.1 #per second, scales with temperature & other constants - - type: Humanoid + - type: HumanoidAppearance species: Human - type: Body prototype: Human @@ -364,7 +364,7 @@ - MobMask layer: - MobLayer - - type: Humanoid + - type: HumanoidAppearance species: Human - type: Body prototype: Human diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 5c58efab1b..2e7f594044 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -5,7 +5,7 @@ id: BaseMobDiona abstract: true components: - - type: Humanoid + - type: HumanoidAppearance species: Diona - type: Hunger - type: Thirst @@ -61,5 +61,5 @@ components: - type: Inventory templateId: diona - - type: Humanoid + - type: HumanoidAppearance species: Diona diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index 3ba3fee94f..0b66aac1ac 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -5,7 +5,7 @@ id: BaseMobReptilian abstract: true components: - - type: Humanoid + - type: HumanoidAppearance species: Reptilian - type: Hunger - type: Thirst @@ -56,7 +56,7 @@ noSpawn: true description: A dummy reptilian meant to be used in character setup. components: - - type: Humanoid + - type: HumanoidAppearance species: Reptilian #Weh diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index 80bf91f230..76993aad60 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -5,7 +5,7 @@ id: BaseMobSkeletonPerson abstract: true components: - - type: Humanoid + - type: HumanoidAppearance species: Skeleton - type: Icon sprite: Mobs/Species/Skeleton/parts.rsi @@ -70,5 +70,5 @@ noSpawn: true description: A dummy skeleton meant to be used in character setup. components: - - type: Humanoid + - type: HumanoidAppearance species: Skeleton diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index d43fa6d8a4..4ec2278655 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -12,7 +12,7 @@ - type: Body prototype: Slime requiredLegs: 2 - - type: Humanoid + - type: HumanoidAppearance species: SlimePerson - type: Speech speechSounds: Slime @@ -81,5 +81,5 @@ noSpawn: true description: A dummy slime meant to be used in character setup. components: - - type: Humanoid + - type: HumanoidAppearance species: SlimePerson diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index 499dc2587a..7f27787625 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -90,7 +90,7 @@ damageRecovery: types: Asphyxiation: -1.0 - - type: Humanoid + - type: HumanoidAppearance species: Vox # canColorHair: false # canColorFacialHair: false @@ -107,7 +107,7 @@ parent: MobHumanDummy noSpawn: true components: - - type: Humanoid + - type: HumanoidAppearance species: Vox - type: Body prototype: Vox