Files
tbd-station-14/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Nemanja 161fd6c83c Mega Antag Refactor (#25786)
* Mega Antag Refactor

* last minute delta save

* more workshopping

* more shit

* ok tested this for once

* okkkkk sure

* generic delays for starting rules

* well darn

* nukies partially

* ouagh

* ballin' faded and smonkin wed

* obliterated the diff

* Spread my arms and soak up congratulations

* I've got plenty of love, but nothing to show for it

* but there’s too much sunlight
Shining on my laptop monitor, so I
Can’t see anything with any amount of clarity

* ok this junk

* OOK!

* fubar

* most of sloth's review

* oh boy

* eek

* hell yea!

* ASDFJASDJFvsakcvjkzjnhhhyh
2024-04-25 11:31:45 +10:00

378 lines
14 KiB
C#

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.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!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HumanoidAppearanceComponent, AfterAutoHandleStateEvent>(OnHandleState);
}
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args)
{
UpdateSprite(component, Comp<SpriteComponent>(uid));
}
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.Contains(layer) || humanoid.PermanentlyHidden.Contains(layer);
private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
{
var oldLayers = new HashSet<HumanoidVisualLayers>(component.BaseLayers.Keys);
component.BaseLayers.Clear();
// add default species layers
var speciesProto = _prototypeManager.Index(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 HashSet<HumanoidVisualLayers>();
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);
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);
}
}
humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet);
}
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 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);
}
}
protected override void SetLayerVisibility(
EntityUid uid,
HumanoidAppearanceComponent humanoid,
HumanoidVisualLayers layer,
bool visible,
bool permanent,
ref bool dirty)
{
base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
var sprite = Comp<SpriteComponent>(uid);
if (!sprite.LayerMapTryGet(layer, out var index))
{
if (!visible)
return;
else
index = sprite.LayerMapReserveBlank(layer);
}
var spriteLayer = sprite[index];
if (spriteLayer.Visible == visible)
return;
spriteLayer.Visible = visible;
// I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
{
foreach (var marking in markingList)
{
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
}
}
}
}