Makes humanoid appearance component networked. (#13009)
Fixes https://github.com/space-wizards/space-station-14/issues/12248
This commit is contained in:
351
Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Normal file
351
Content.Client/Humanoid/HumanoidAppearanceSystem.cs
Normal file
@@ -0,0 +1,351 @@
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.Humanoid.HumanoidAppearanceState;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not HumanoidAppearanceState state)
|
||||
return;
|
||||
|
||||
ApplyState(uid, component, Comp<SpriteComponent>(uid), state);
|
||||
}
|
||||
|
||||
private void ApplyState(EntityUid uid, HumanoidAppearanceComponent component, SpriteComponent sprite, HumanoidAppearanceState state)
|
||||
{
|
||||
component.Sex = state.Sex;
|
||||
component.Species = state.Species;
|
||||
component.Age = state.Age;
|
||||
component.SkinColor = state.SkinColor;
|
||||
component.EyeColor = state.EyeColor;
|
||||
component.HiddenLayers = new(state.HiddenLayers);
|
||||
component.PermanentlyHidden = new(state.PermanentlyHidden);
|
||||
|
||||
component.CustomBaseLayers = state.CustomBaseLayers.ShallowClone();
|
||||
UpdateLayers(component, sprite);
|
||||
|
||||
ApplyMarkingSet(uid, state.Markings, component, sprite);
|
||||
|
||||
sprite[sprite.LayerMapReserveBlank(HumanoidVisualLayers.Eyes)].Color = state.EyeColor;
|
||||
}
|
||||
|
||||
private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
|
||||
=> humanoid.HiddenLayers.Contains(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
|
||||
private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
|
||||
{
|
||||
var oldLayers = new HashSet<HumanoidVisualLayers>(component.BaseLayers.Keys);
|
||||
component.BaseLayers.Clear();
|
||||
|
||||
// add default species layers
|
||||
var speciesProto = _prototypeManager.Index<SpeciesPrototype>(component.Species);
|
||||
var baseSprites = _prototypeManager.Index<HumanoidSpeciesBaseSpritesPrototype>(speciesProto.SpriteSet);
|
||||
foreach (var (key, id) in baseSprites.Sprites)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
if (!component.CustomBaseLayers.ContainsKey(key))
|
||||
SetLayerData(component, sprite, key, id, sexMorph: true);
|
||||
}
|
||||
|
||||
// add custom layers
|
||||
foreach (var (key, info) in component.CustomBaseLayers)
|
||||
{
|
||||
oldLayers.Remove(key);
|
||||
SetLayerData(component, sprite, key, info.ID, sexMorph: false, color: info.Color); ;
|
||||
}
|
||||
|
||||
// hide old layers
|
||||
// TODO maybe just remove them altogether?
|
||||
foreach (var key in oldLayers)
|
||||
{
|
||||
if (sprite.LayerMapTryGet(key, out var index))
|
||||
sprite[index].Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLayerData(
|
||||
HumanoidAppearanceComponent component,
|
||||
SpriteComponent sprite,
|
||||
HumanoidVisualLayers key,
|
||||
string protoId,
|
||||
bool sexMorph = false,
|
||||
Color? color = null)
|
||||
{
|
||||
if (sexMorph)
|
||||
protoId = HumanoidVisualLayersExtension.GetSexMorph(key, component.Sex, protoId);
|
||||
|
||||
var proto = _prototypeManager.Index<HumanoidSpeciesSpriteLayer>(protoId);
|
||||
component.BaseLayers[key] = proto;
|
||||
|
||||
var layerIndex = sprite.LayerMapReserveBlank(key);
|
||||
var layer = sprite[layerIndex];
|
||||
|
||||
if (color != null)
|
||||
layer.Color = color.Value;
|
||||
else if (proto.MatchSkin)
|
||||
layer.Color = proto.MatchSkin ? component.SkinColor.WithAlpha(proto.LayerAlpha) : Color.White;
|
||||
|
||||
if (proto.BaseSprite != null)
|
||||
sprite.LayerSetSprite(layerIndex, proto.BaseSprite);
|
||||
|
||||
layer.Visible = !IsHidden(component, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a profile directly into a humanoid.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid entity's UID</param>
|
||||
/// <param name="profile">The profile to load.</param>
|
||||
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
|
||||
/// <remarks>
|
||||
/// This should not be used if the entity is owned by the server. The server will otherwise
|
||||
/// override this with the appearance data it sends over.
|
||||
/// </remarks>
|
||||
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>();
|
||||
|
||||
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
||||
var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
|
||||
_prototypeManager);
|
||||
markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
|
||||
|
||||
// legacy: remove in the future?
|
||||
markings.RemoveCategory(MarkingCategories.Hair);
|
||||
markings.RemoveCategory(MarkingCategories.FacialHair);
|
||||
|
||||
var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
|
||||
markings.AddBack(MarkingCategories.Hair, hair);
|
||||
|
||||
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
|
||||
new[] { profile.Appearance.FacialHairColor });
|
||||
markings.AddBack(MarkingCategories.FacialHair, facialHair);
|
||||
|
||||
markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
|
||||
|
||||
DebugTools.Assert(uid.IsClientSide());
|
||||
|
||||
var state = new HumanoidAppearanceState(markings,
|
||||
new(),
|
||||
new(),
|
||||
customBaseLayers,
|
||||
profile.Sex,
|
||||
profile.Gender,
|
||||
profile.Age,
|
||||
profile.Species,
|
||||
profile.Appearance.SkinColor,
|
||||
profile.Appearance.EyeColor);
|
||||
|
||||
ApplyState(uid, humanoid, Comp<SpriteComponent>(uid), state);
|
||||
}
|
||||
|
||||
private void ApplyMarkingSet(EntityUid uid,
|
||||
MarkingSet newMarkings,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
// skip this entire thing if both sets are empty
|
||||
if (humanoid.MarkingSet.Markings.Count == 0 && newMarkings.Markings.Count == 0)
|
||||
return;
|
||||
|
||||
// I am lazy and I CBF resolving the previous mess, so I'm just going to nuke the markings.
|
||||
// Really, markings should probably be a separate component altogether.
|
||||
|
||||
ClearAllMarkings(uid, humanoid, sprite);
|
||||
|
||||
humanoid.MarkingSet = new(newMarkings);
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(EntityUid uid, HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
RemoveMarking(uid, marking, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearMarkings(EntityUid uid, List<Marking> markings, HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
RemoveMarking(uid, marking, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMarking(EntityUid uid, Marking marking,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sprite in prototype.Sprites)
|
||||
{
|
||||
if (sprite is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
|
||||
if (!spriteComp.LayerMapTryGet(layerId, out var index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
spriteComp.LayerMapRemove(layerId);
|
||||
spriteComp.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(EntityUid uid,
|
||||
MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
visible &= !IsHidden(humanoid, markingPrototype.BodyPart);
|
||||
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
|
||||
&& setting.AllowsMarkings;
|
||||
|
||||
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
|
||||
{
|
||||
if (markingPrototype.Sprites[j] is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (!sprite.LayerMapTryGet(layerId, out _))
|
||||
{
|
||||
var layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
|
||||
sprite.LayerMapSet(layerId, layer);
|
||||
sprite.LayerSetSprite(layerId, rsi);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(layerId, visible);
|
||||
|
||||
if (!visible || setting == null) // this is kinda implied
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
|
||||
{
|
||||
var skinColor = humanoid.SkinColor;
|
||||
skinColor.A = setting.LayerAlpha;
|
||||
|
||||
sprite.LayerSetColor(layerId, skinColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetColor(layerId, colors[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetSkinColor(EntityUid uid, Color skinColor, bool sync = true, HumanoidAppearanceComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid) || humanoid.SkinColor == skinColor)
|
||||
return;
|
||||
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
if (sync)
|
||||
Dirty(humanoid);
|
||||
|
||||
if (!TryComp(uid, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!spriteInfo.MatchSkin)
|
||||
continue;
|
||||
|
||||
var index = sprite.LayerMapReserveBlank(layer);
|
||||
sprite[index].Color = skinColor.WithAlpha(spriteInfo.LayerAlpha);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetLayerVisibility(
|
||||
EntityUid uid,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent,
|
||||
ref bool dirty)
|
||||
{
|
||||
base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
|
||||
var sprite = Comp<SpriteComponent>(uid);
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
if (!visible)
|
||||
return;
|
||||
else
|
||||
index = sprite.LayerMapReserveBlank(layer);
|
||||
}
|
||||
|
||||
var spriteLayer = sprite[index];
|
||||
if (spriteLayer.Visible == visible)
|
||||
return;
|
||||
|
||||
spriteLayer.Visible = visible;
|
||||
|
||||
// I fucking hate this. I'll get around to refactoring sprite layers eventually I swear
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype) && markingPrototype.BodyPart == layer)
|
||||
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidSystem : SharedHumanoidSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a profile directly into a humanoid.
|
||||
/// </summary>
|
||||
/// <param name="uid">The humanoid entity's UID</param>
|
||||
/// <param name="profile">The profile to load.</param>
|
||||
/// <param name="humanoid">The humanoid entity's humanoid component.</param>
|
||||
/// <remarks>
|
||||
/// This should not be used if the entity is owned by the server. The server will otherwise
|
||||
/// override this with the appearance data it sends over.
|
||||
/// </remarks>
|
||||
public void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidComponent? humanoid = null)
|
||||
{
|
||||
if (!Resolve(uid, ref humanoid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
humanoid.Species = profile.Species;
|
||||
var customBaseLayers = new Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>
|
||||
{
|
||||
[HumanoidVisualLayers.Eyes] = new CustomBaseLayerInfo(string.Empty, profile.Appearance.EyeColor)
|
||||
};
|
||||
|
||||
var speciesPrototype = _prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
||||
var markings = new MarkingSet(profile.Appearance.Markings, speciesPrototype.MarkingPoints, _markingManager,
|
||||
_prototypeManager);
|
||||
markings.EnsureDefault(profile.Appearance.SkinColor, _markingManager);
|
||||
|
||||
// legacy: remove in the future?
|
||||
markings.RemoveCategory(MarkingCategories.Hair);
|
||||
markings.RemoveCategory(MarkingCategories.FacialHair);
|
||||
|
||||
var hair = new Marking(profile.Appearance.HairStyleId, new[] { profile.Appearance.HairColor });
|
||||
markings.AddBack(MarkingCategories.Hair, hair);
|
||||
|
||||
var facialHair = new Marking(profile.Appearance.FacialHairStyleId,
|
||||
new[] { profile.Appearance.FacialHairColor });
|
||||
markings.AddBack(MarkingCategories.FacialHair, facialHair);
|
||||
|
||||
markings.FilterSpecies(profile.Species, _markingManager, _prototypeManager);
|
||||
|
||||
SetAppearance(uid,
|
||||
profile.Species,
|
||||
customBaseLayers,
|
||||
profile.Appearance.SkinColor,
|
||||
profile.Sex,
|
||||
new(), // doesn't exist yet
|
||||
markings.GetForwardEnumerator().ToList());
|
||||
}
|
||||
}
|
||||
@@ -1,447 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Humanoid;
|
||||
|
||||
public sealed class HumanoidVisualizerSystem : VisualizerSystem<HumanoidComponent>
|
||||
{
|
||||
[Dependency] private IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private MarkingManager _markingManager = default!;
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, HumanoidComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
base.OnAppearanceChange(uid, component, ref args);
|
||||
|
||||
if (args.Sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(HumanoidVisualizerKey.Key, out var dataRaw)
|
||||
|| dataRaw is not HumanoidVisualizerData data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(data.Species, out SpeciesPrototype? speciesProto)
|
||||
|| !_prototypeManager.TryIndex(speciesProto.SpriteSet, out HumanoidSpeciesBaseSpritesPrototype? baseSprites))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dirty = data.SkinColor != component.SkinColor || data.Sex != component.Sex;
|
||||
component.Sex = data.Sex;
|
||||
|
||||
if (data.CustomBaseLayerInfo.Count != 0)
|
||||
{
|
||||
dirty |= MergeCustomBaseSprites(uid, baseSprites.Sprites, data.CustomBaseLayerInfo, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
dirty |= MergeCustomBaseSprites(uid, baseSprites.Sprites, null, component);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
ApplyBaseSprites(uid, component, args.Sprite);
|
||||
ApplySkinColor(uid, data.SkinColor, component, args.Sprite);
|
||||
}
|
||||
|
||||
if (data.CustomBaseLayerInfo.Count != 0)
|
||||
{
|
||||
foreach (var (layer, info) in data.CustomBaseLayerInfo)
|
||||
{
|
||||
SetBaseLayerColor(uid, layer, info.Color, args.Sprite);
|
||||
}
|
||||
}
|
||||
|
||||
var layerVis = data.LayerVisibility.ToHashSet();
|
||||
dirty |= ReplaceHiddenLayers(uid, layerVis, component, args.Sprite);
|
||||
|
||||
DiffAndApplyMarkings(uid, data.Markings, dirty, component, args.Sprite);
|
||||
}
|
||||
|
||||
private bool ReplaceHiddenLayers(EntityUid uid, HashSet<HumanoidVisualLayers> hiddenLayers,
|
||||
HumanoidComponent humanoid, SpriteComponent sprite)
|
||||
{
|
||||
if (hiddenLayers.SetEquals(humanoid.HiddenLayers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetSpriteVisibility(uid, hiddenLayers, false, sprite);
|
||||
|
||||
humanoid.HiddenLayers.ExceptWith(hiddenLayers);
|
||||
|
||||
SetSpriteVisibility(uid, humanoid.HiddenLayers, true, sprite);
|
||||
|
||||
humanoid.HiddenLayers.Clear();
|
||||
humanoid.HiddenLayers.UnionWith(hiddenLayers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetSpriteVisibility(EntityUid uid, HashSet<HumanoidVisualLayers> layers, bool visibility, SpriteComponent sprite)
|
||||
{
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sprite[index].Visible = visibility;
|
||||
}
|
||||
}
|
||||
|
||||
private void DiffAndApplyMarkings(EntityUid uid,
|
||||
List<Marking> newMarkings,
|
||||
bool layersDirty,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
// skip this entire thing if both sets are empty
|
||||
if (humanoid.CurrentClientMarkings.Count == 0 && newMarkings.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dirtyMarkings = new List<int>();
|
||||
var dirtyRangeStart = humanoid.CurrentClientMarkings.Count == 0 ? 0 : -1;
|
||||
|
||||
// edge cases:
|
||||
// humanoid.CurrentClientMarkings < newMarkings.Count
|
||||
// - check if count matches this condition before diffing
|
||||
// - if count is unequal, set dirty range to start from humanoid.CurrentClientMarkings.Count
|
||||
// humanoid.CurrentClientMarkings > newMarkings.Count, no dirty markings
|
||||
// - break count upon meeting this condition
|
||||
// - clear markings from newMarkings.Count to humanoid.CurrentClientMarkings.Count - newMarkings.Count
|
||||
|
||||
for (var i = 0; i < humanoid.CurrentClientMarkings.Count; i++)
|
||||
{
|
||||
// if we've reached the end of the new set of markings,
|
||||
// then that means it's time to finish
|
||||
if (newMarkings.Count == i)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// if the marking is different here, set the range start to i and break, we need
|
||||
// to rebuild all markings starting from i
|
||||
if (humanoid.CurrentClientMarkings[i].MarkingId != newMarkings[i].MarkingId)
|
||||
{
|
||||
dirtyRangeStart = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise, we add the current marking to dirtyMarkings if it has different
|
||||
// settings
|
||||
// however: if the hidden layers are set to dirty, then we need to
|
||||
// instead just add every single marking, since we don't know ahead of time
|
||||
// where these markings go
|
||||
if (humanoid.CurrentClientMarkings[i] != newMarkings[i] || layersDirty)
|
||||
{
|
||||
dirtyMarkings.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var i in dirtyMarkings)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(newMarkings[i], out var dirtyMarking))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyMarking(uid, dirtyMarking, newMarkings[i].MarkingColors, newMarkings[i].Visible, humanoid, sprite);
|
||||
}
|
||||
|
||||
if (humanoid.CurrentClientMarkings.Count < newMarkings.Count && dirtyRangeStart < 0)
|
||||
{
|
||||
dirtyRangeStart = humanoid.CurrentClientMarkings.Count;
|
||||
}
|
||||
|
||||
if (dirtyRangeStart >= 0)
|
||||
{
|
||||
var range = newMarkings.GetRange(dirtyRangeStart, newMarkings.Count - dirtyRangeStart);
|
||||
|
||||
if (humanoid.CurrentClientMarkings.Count > 0)
|
||||
{
|
||||
var oldRange = humanoid.CurrentClientMarkings.GetRange(dirtyRangeStart, humanoid.CurrentClientMarkings.Count - dirtyRangeStart);
|
||||
ClearMarkings(uid, oldRange, humanoid, sprite);
|
||||
}
|
||||
|
||||
ApplyMarkings(uid, range, humanoid, sprite);
|
||||
}
|
||||
else if (humanoid.CurrentClientMarkings.Count != newMarkings.Count)
|
||||
{
|
||||
if (newMarkings.Count == 0)
|
||||
{
|
||||
ClearAllMarkings(uid, humanoid, sprite);
|
||||
}
|
||||
else if (humanoid.CurrentClientMarkings.Count > newMarkings.Count)
|
||||
{
|
||||
var rangeStart = newMarkings.Count;
|
||||
var rangeCount = humanoid.CurrentClientMarkings.Count - newMarkings.Count;
|
||||
var range = humanoid.CurrentClientMarkings.GetRange(rangeStart, rangeCount);
|
||||
|
||||
ClearMarkings(uid, range, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirtyMarkings.Count > 0 || dirtyRangeStart >= 0 || humanoid.CurrentClientMarkings.Count != newMarkings.Count)
|
||||
{
|
||||
humanoid.CurrentClientMarkings = newMarkings;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(EntityUid uid, HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
ClearMarkings(uid, humanoid.CurrentClientMarkings, humanoid, spriteComp);
|
||||
}
|
||||
|
||||
private void ClearMarkings(EntityUid uid, List<Marking> markings, HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var marking in markings)
|
||||
{
|
||||
RemoveMarking(uid, marking, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMarking(EntityUid uid, Marking marking,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var sprite in prototype.Sprites)
|
||||
{
|
||||
if (sprite is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{marking.MarkingId}-{rsi.RsiState}";
|
||||
if (!spriteComp.LayerMapTryGet(layerId, out var index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
spriteComp.LayerMapRemove(layerId);
|
||||
spriteComp.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarkings(EntityUid uid,
|
||||
List<Marking> markings,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var marking in new ReverseMarkingEnumerator(markings))
|
||||
{
|
||||
if (!_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyMarking(uid, markingPrototype, marking.MarkingColors, marking.Visible, humanoid, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(EntityUid uid,
|
||||
MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(markingPrototype.BodyPart, out int targetLayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
visible &= !humanoid.HiddenLayers.Contains(markingPrototype.BodyPart);
|
||||
visible &= humanoid.BaseLayers.TryGetValue(markingPrototype.BodyPart, out var setting)
|
||||
&& setting.AllowsMarkings;
|
||||
|
||||
for (var j = 0; j < markingPrototype.Sprites.Count; j++)
|
||||
{
|
||||
if (markingPrototype.Sprites[j] is not SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var layerId = $"{markingPrototype.ID}-{rsi.RsiState}";
|
||||
|
||||
if (!sprite.LayerMapTryGet(layerId, out _))
|
||||
{
|
||||
var layer = sprite.AddLayer(markingPrototype.Sprites[j], targetLayer + j + 1);
|
||||
sprite.LayerMapSet(layerId, layer);
|
||||
sprite.LayerSetSprite(layerId, rsi);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(layerId, visible);
|
||||
|
||||
if (!visible || setting == null) // this is kinda implied
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (markingPrototype.FollowSkinColor || colors == null || setting.MarkingsMatchSkin)
|
||||
{
|
||||
var skinColor = humanoid.SkinColor;
|
||||
skinColor.A = setting.LayerAlpha;
|
||||
|
||||
sprite.LayerSetColor(layerId, skinColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetColor(layerId, colors[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySkinColor(EntityUid uid,
|
||||
Color skinColor,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!spriteInfo.MatchSkin)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = skinColor;
|
||||
color.A = spriteInfo.LayerAlpha;
|
||||
|
||||
SetBaseLayerColor(uid, layer, color, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBaseLayerColor(EntityUid uid, HumanoidVisualLayers layer, Color color,
|
||||
SpriteComponent sprite)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprite[index].Color = color;
|
||||
}
|
||||
|
||||
private bool MergeCustomBaseSprites(EntityUid uid, Dictionary<HumanoidVisualLayers, string> baseSprites,
|
||||
Dictionary<HumanoidVisualLayers, CustomBaseLayerInfo>? customBaseSprites,
|
||||
HumanoidComponent humanoid)
|
||||
{
|
||||
var newBaseLayers = new Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer>();
|
||||
|
||||
foreach (var (key, id) in baseSprites)
|
||||
{
|
||||
var sexMorph = humanoid.Sex switch
|
||||
{
|
||||
Sex.Male when HumanoidVisualLayersExtension.HasSexMorph(key) => $"{id}Male",
|
||||
Sex.Female when HumanoidVisualLayersExtension.HasSexMorph(key) => $"{id}Female",
|
||||
_ => id
|
||||
};
|
||||
|
||||
if (!_prototypeManager.TryIndex(sexMorph, out HumanoidSpeciesSpriteLayer? baseLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newBaseLayers.TryAdd(key, baseLayer))
|
||||
{
|
||||
newBaseLayers[key] = baseLayer;
|
||||
}
|
||||
}
|
||||
|
||||
if (customBaseSprites == null)
|
||||
{
|
||||
return IsDirty(newBaseLayers);
|
||||
}
|
||||
|
||||
foreach (var (key, info) in customBaseSprites)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(info.ID, out HumanoidSpeciesSpriteLayer? baseLayer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!newBaseLayers.TryAdd(key, baseLayer))
|
||||
{
|
||||
newBaseLayers[key] = baseLayer;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDirty(Dictionary<HumanoidVisualLayers, HumanoidSpeciesSpriteLayer> newBaseLayers)
|
||||
{
|
||||
var dirty = false;
|
||||
if (humanoid.BaseLayers.Count != newBaseLayers.Count)
|
||||
{
|
||||
dirty = true;
|
||||
humanoid.BaseLayers = newBaseLayers;
|
||||
return dirty;
|
||||
}
|
||||
|
||||
foreach (var (key, info) in humanoid.BaseLayers)
|
||||
{
|
||||
if (!newBaseLayers.TryGetValue(key, out var newInfo))
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (info.ID != newInfo.ID)
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
humanoid.BaseLayers = newBaseLayers;
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
return IsDirty(newBaseLayers);
|
||||
}
|
||||
|
||||
private void ApplyBaseSprites(EntityUid uid,
|
||||
HumanoidComponent humanoid,
|
||||
SpriteComponent spriteComp)
|
||||
{
|
||||
foreach (var (layer, spriteInfo) in humanoid.BaseLayers)
|
||||
{
|
||||
if (spriteInfo.BaseSprite != null && spriteComp.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
switch (spriteInfo.BaseSprite)
|
||||
{
|
||||
case SpriteSpecifier.Rsi rsi:
|
||||
spriteComp.LayerSetRSI(index, rsi.RsiPath);
|
||||
spriteComp.LayerSetState(index, rsi.RsiState);
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
spriteComp.LayerSetTexture(index, texture.TexturePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public sealed partial class MarkingPicker : Control
|
||||
|
||||
private List<MarkingCategories> _markingCategories = Enum.GetValues<MarkingCategories>().ToList();
|
||||
|
||||
private string _currentSpecies = SharedHumanoidSystem.DefaultSpecies;
|
||||
private string _currentSpecies = SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
public Color CurrentSkinColor = Color.White;
|
||||
|
||||
private readonly HashSet<MarkingCategories> _ignoreCategories = new();
|
||||
@@ -361,12 +361,13 @@ public sealed partial class MarkingPicker : Control
|
||||
colorContainer.AddChild(new Label { Text = $"{stateNames[i]} color:" });
|
||||
colorContainer.AddChild(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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user