| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- using Content.Shared.CCVar;
- using Content.Shared.Humanoid;
- using Content.Shared.Humanoid.Markings;
- using Content.Shared.Humanoid.Prototypes;
- using Content.Shared.Inventory;
- using Content.Shared.Preferences;
- using Robust.Client.GameObjects;
- using Robust.Shared.Configuration;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Utility;
- namespace Content.Client.Humanoid;
- public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
- {
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly MarkingManager _markingManager = default!;
- [Dependency] private readonly IConfigurationManager _configurationManager = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<HumanoidAppearanceComponent, AfterAutoHandleStateEvent>(OnHandleState);
- Subs.CVar(_configurationManager, CCVars.AccessibilityClientCensorNudity, OnCvarChanged, true);
- Subs.CVar(_configurationManager, CCVars.AccessibilityServerCensorNudity, OnCvarChanged, true);
- }
- private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args)
- {
- UpdateSprite(component, Comp<SpriteComponent>(uid));
- }
- private void OnCvarChanged(bool value)
- {
- var humanoidQuery = EntityManager.AllEntityQueryEnumerator<HumanoidAppearanceComponent, SpriteComponent>();
- while (humanoidQuery.MoveNext(out var _, out var humanoidComp, out var spriteComp))
- {
- UpdateSprite(humanoidComp, spriteComp);
- }
- }
- private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent sprite)
- {
- UpdateLayers(component, sprite);
- ApplyMarkingSet(component, sprite);
- sprite[sprite.LayerMapReserveBlank(HumanoidVisualLayers.Eyes)].Color = component.EyeColor;
- }
- private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
- => humanoid.HiddenLayers.ContainsKey(layer) || humanoid.PermanentlyHidden.Contains(layer);
- private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
- {
- var oldLayers = new HashSet<HumanoidVisualLayers>(component.BaseLayers.Keys);
- component.BaseLayers.Clear();
- // add default species layers
- var speciesProto = _prototypeManager.Index(component.Species);
- var baseSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>(speciesProto.SpriteSet);
- foreach (var (key, id) in baseSprites.Sprites)
- {
- oldLayers.Remove(key);
- if (!component.CustomBaseLayers.ContainsKey(key))
- SetLayerData(component, sprite, key, id, sexMorph: true);
- }
- // add custom layers
- foreach (var (key, info) in component.CustomBaseLayers)
- {
- oldLayers.Remove(key);
- SetLayerData(component, sprite, key, info.Id, sexMorph: false, color: info.Color);
- }
- // hide old layers
- // TODO maybe just remove them altogether?
- foreach (var key in oldLayers)
- {
- if (sprite.LayerMapTryGet(key, out var index))
- sprite[index].Visible = false;
- }
- }
- private void SetLayerData(
- HumanoidAppearanceComponent component,
- SpriteComponent sprite,
- HumanoidVisualLayers key,
- string? protoId,
- bool sexMorph = false,
- Color? color = null)
- {
- var layerIndex = sprite.LayerMapReserveBlank(key);
- var layer = sprite[layerIndex];
- layer.Visible = !IsHidden(component, key);
- if (color != null)
- layer.Color = color.Value;
- if (protoId == null)
- return;
- if (sexMorph)
- protoId = HumanoidVisualLayersExtension.GetSexMorph(key, component.Sex, protoId);
- var proto = _prototypeManager.Index<HumanoidSpeciesSpriteLayer>(protoId);
- component.BaseLayers[key] = proto;
- if (proto.MatchSkin)
- layer.Color = component.SkinColor.WithAlpha(proto.LayerAlpha);
- if (proto.BaseSprite != null)
- sprite.LayerSetSprite(layerIndex, proto.BaseSprite);
- }
- /// <summary>
- /// Loads a profile directly into a humanoid.
- /// </summary>
- /// <param name="uid">The humanoid entity's UID</param>
- /// <param name="profile">The profile to load.</param>
- /// <param name="humanoid">The humanoid entity's humanoid component.</param>
- /// <remarks>
- /// This should not be used if the entity is owned by the server. The server will otherwise
- /// override this with the appearance data it sends over.
- /// </remarks>
- public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profile, HumanoidAppearanceComponent? humanoid = null)
- {
- if (profile == null)
- return;
- if (!Resolve(uid, ref humanoid))
- {
- return;
- }
- var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>();
- var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
- var markings = new MarkingSet(speciesPrototype.MarkingPoints, _markingManager, _prototypeManager);
- // Add markings that doesn't need coloring. We store them until we add all other markings that doesn't need it.
- var markingFColored = new Dictionary<Marking, MarkingPrototype>();
- foreach (var marking in profile.Appearance.Markings)
- {
- if (_markingManager.TryGetMarking(marking, out var prototype))
- {
- if (!prototype.ForcedColoring)
- {
- markings.AddBack(prototype.MarkingCategory, marking);
- }
- else
- {
- markingFColored.Add(marking, prototype);
- }
- }
- }
- // legacy: remove in the future?
- //markings.RemoveCategory(MarkingCategories.Hair);
- //markings.RemoveCategory(MarkingCategories.FacialHair);
- // We need to ensure hair before applying it or coloring can try depend on markings that can be invalid
- var hairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.Hair, out var hairAlpha, _prototypeManager)
- ? profile.Appearance.SkinColor.WithAlpha(hairAlpha)
- : profile.Appearance.HairColor;
- var hair = new Marking(profile.Appearance.HairStyleId,
- new[] { hairColor });
- var facialHairColor = _markingManager.MustMatchSkin(profile.Species, HumanoidVisualLayers.FacialHair, out var facialHairAlpha, _prototypeManager)
- ? profile.Appearance.SkinColor.WithAlpha(facialHairAlpha)
- : profile.Appearance.FacialHairColor;
- var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
- new[] { facialHairColor });
- if (_markingManager.CanBeApplied(profile.Species, profile.Sex, hair, _prototypeManager))
- {
- markings.AddBack(MarkingCategories.Hair, hair);
- }
- if (_markingManager.CanBeApplied(profile.Species, profile.Sex, facialHair, _prototypeManager))
- {
- markings.AddBack(MarkingCategories.FacialHair, facialHair);
- }
- // Finally adding marking with forced colors
- foreach (var (marking, prototype) in markingFColored)
- {
- var markingColors = MarkingColoring.GetMarkingLayerColors(
- prototype,
- profile.Appearance.SkinColor,
- profile.Appearance.EyeColor,
- markings
- );
- markings.AddBack(prototype.MarkingCategory, new Marking(marking.MarkingId, markingColors));
- }
- markings.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _prototypeManager);
- markings.EnsureSexes(profile.Sex, _markingManager);
- markings.EnsureDefault(
- profile.Appearance.SkinColor,
- profile.Appearance.EyeColor,
- _markingManager);
- DebugTools.Assert(IsClientSide(uid));
- humanoid.MarkingSet = markings;
- humanoid.PermanentlyHidden = new HashSet<HumanoidVisualLayers>();
- humanoid.HiddenLayers = new Dictionary<HumanoidVisualLayers, SlotFlags>();
- humanoid.CustomBaseLayers = customBaseLayers;
- humanoid.Sex = profile.Sex;
- humanoid.Gender = profile.Gender;
- humanoid.Age = profile.Age;
- humanoid.Species = profile.Species;
- humanoid.SkinColor = profile.Appearance.SkinColor;
- humanoid.EyeColor = profile.Appearance.EyeColor;
- UpdateSprite(humanoid, Comp<SpriteComponent>(uid));
- }
- private void ApplyMarkingSet(HumanoidAppearanceComponent humanoid, SpriteComponent sprite)
- {
- // 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(humanoid, sprite);
- var censorNudity = _configurationManager.GetCVar(CCVars.AccessibilityClientCensorNudity) ||
- _configurationManager.GetCVar(CCVars.AccessibilityServerCensorNudity);
- // The reason we're splitting this up is in case the character already has undergarment equipped in that slot.
- var applyUndergarmentTop = censorNudity;
- var applyUndergarmentBottom = censorNudity;
- foreach (var markingList in humanoid.MarkingSet.Markings.Values)
- {
- foreach (var marking in markingList)
- {
- if (_markingManager.TryGetMarking(marking, out var markingPrototype))
- {
- ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
- if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
- applyUndergarmentTop = false;
- else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
- applyUndergarmentBottom = false;
- }
- }
- }
- humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet);
- AddUndergarments(humanoid, sprite, applyUndergarmentTop, applyUndergarmentBottom);
- }
- private void ClearAllMarkings(HumanoidAppearanceComponent humanoid, SpriteComponent sprite)
- {
- foreach (var markingList in humanoid.ClientOldMarkings.Markings.Values)
- {
- foreach (var marking in markingList)
- {
- RemoveMarking(marking, sprite);
- }
- }
- humanoid.ClientOldMarkings.Clear();
- foreach (var markingList in humanoid.MarkingSet.Markings.Values)
- {
- foreach (var marking in markingList)
- {
- RemoveMarking(marking, sprite);
- }
- }
- }
- private void RemoveMarking(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 AddUndergarments(HumanoidAppearanceComponent humanoid, SpriteComponent sprite, bool undergarmentTop, bool undergarmentBottom)
- {
- if (undergarmentTop && humanoid.UndergarmentTop != null)
- {
- var marking = new Marking(humanoid.UndergarmentTop, new List<Color> { new Color() });
- if (_markingManager.TryGetMarking(marking, out var prototype))
- {
- // Markings are added to ClientOldMarkings because otherwise it causes issues when toggling the feature on/off.
- humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentTop, new List<Marking>{ marking });
- ApplyMarking(prototype, null, true, humanoid, sprite);
- }
- }
- if (undergarmentBottom && humanoid.UndergarmentBottom != null)
- {
- var marking = new Marking(humanoid.UndergarmentBottom, new List<Color> { new Color() });
- if (_markingManager.TryGetMarking(marking, out var prototype))
- {
- humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentBottom, new List<Marking>{ marking });
- ApplyMarking(prototype, null, true, humanoid, sprite);
- }
- }
- }
- private void ApplyMarking(MarkingPrototype markingPrototype,
- IReadOnlyList<Color>? colors,
- bool visible,
- HumanoidAppearanceComponent humanoid,
- SpriteComponent sprite)
- {
- if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
- {
- return;
- }
- visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
- visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
- && setting.AllowsMarkings;
- for (var j = 0; j < markingPrototype.Sprites.Count; j++)
- {
- var markingSprite = markingPrototype.Sprites[j];
- if (markingSprite is not SpriteSpecifier.Rsi rsi)
- {
- continue;
- }
- var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
- if (!sprite.LayerMapTryGet(layerId, out _))
- {
- var layer = sprite.AddLayer(markingSprite, targetLayer + j + 1);
- sprite.LayerMapSet(layerId, layer);
- sprite.LayerSetSprite(layerId, rsi);
- }
- sprite.LayerSetVisible(layerId, visible);
- if (!visible || setting == null) // this is kinda implied
- {
- continue;
- }
- // Okay so if the marking prototype is modified but we load old marking data this may no longer be valid
- // and we need to check the index is correct.
- // So if that happens just default to white?
- if (colors != null && j < colors.Count)
- {
- sprite.LayerSetColor(layerId, colors[j]);
- }
- else
- {
- sprite.LayerSetColor(layerId, Color.White);
- }
- }
- }
- public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, bool verify = true, HumanoidAppearanceComponent? humanoid = null)
- {
- if (!Resolve(uid, ref humanoid) || humanoid.SkinColor == skinColor)
- return;
- base.SetSkinColor(uid, skinColor, false, verify, 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);
- }
- }
- public override void SetLayerVisibility(
- Entity<HumanoidAppearanceComponent> ent,
- HumanoidVisualLayers layer,
- bool visible,
- SlotFlags? slot,
- ref bool dirty)
- {
- base.SetLayerVisibility(ent, layer, visible, slot, ref dirty);
- var sprite = Comp<SpriteComponent>(ent);
- if (!sprite.LayerMapTryGet(layer, out var index))
- {
- if (!visible)
- return;
- 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
- // Just a week away...
- foreach (var markingList in ent.Comp.MarkingSet.Markings.Values)
- {
- foreach (var marking in markingList)
- {
- if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
- ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, ent, sprite);
- }
- }
- }
- }
|