Skip to content

Commit

Permalink
Displacement maps big update (space-wizards#30093)
Browse files Browse the repository at this point in the history
* split logic into own system

* add support for different size displacement maps

* some clothes may not use displacement maps

* displacement maps spport hand sprites

* Update DisplacementMapSystem.cs

* rename things

* fuck stencilmask

* fix bugs

* no masks

* Update jumpsuits.yml

* fix species specific sprites

* Update ClothingSystem.cs

* shoes + ears displacement, some bugfix

* Update DisplacementMapSystem.cs
  • Loading branch information
TheShuEd authored Jul 23, 2024
1 parent 4ff3445 commit 918709c
Show file tree
Hide file tree
Showing 26 changed files with 352 additions and 228 deletions.
112 changes: 31 additions & 81 deletions Content.Client/Clothing/ClientClothingSystem.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Content.Client.DisplacementMap;
using Content.Client.Inventory;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.DisplacementMap;
using Content.Shared.Humanoid;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
Expand Down Expand Up @@ -50,6 +52,7 @@ public sealed class ClientClothingSystem : ClothingSystem
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly DisplacementMapSystem _displacement = default!;

public override void Initialize()
{
Expand All @@ -64,15 +67,14 @@ public override void Initialize()

private void OnAppearanceUpdate(EntityUid uid, InventoryComponent component, ref AppearanceChangeEvent args)
{
// May need to update jumpsuit stencils if the sex changed. Also required to properly set the stencil on init
// May need to update displacement maps if the sex changed. Also required to properly set the stencil on init
if (args.Sprite == null)
return;

if (_inventorySystem.TryGetSlotEntity(uid, Jumpsuit, out var suit, component)
&& TryComp(suit, out ClothingComponent? clothing))
var enumerator = _inventorySystem.GetSlotEnumerator((uid, component));
while (enumerator.NextItem(out var item, out var slot))
{
SetGenderedMask(uid, args.Sprite, clothing);
return;
RenderEquipment(uid, item, slot.Name, component);
}

// No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
Expand Down Expand Up @@ -179,14 +181,6 @@ private void OnVisualsChanged(EntityUid uid, InventoryComponent component, Visua

private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEvent args)
{
// Hide jumpsuit mask layer.
if (args.Slot == Jumpsuit
&& TryComp(uid, out SpriteComponent? sprite)
&& sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var maskLayer))
{
sprite.LayerSetVisible(maskLayer, false);
}

if (!TryComp(uid, out InventorySlotsComponent? inventorySlots))
return;

Expand Down Expand Up @@ -231,9 +225,6 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
return;
}

if (slot == Jumpsuit)
SetGenderedMask(equipee, sprite, clothingComponent);

if (!_inventorySystem.TryGetSlot(equipee, slot, out var slotDef, inventory))
return;

Expand Down Expand Up @@ -265,7 +256,25 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
// temporary, until layer draw depths get added. Basically: a layer with the key "slot" is being used as a
// bookmark to determine where in the list of layers we should insert the clothing layers.
bool slotLayerExists = sprite.LayerMapTryGet(slot, out var index);
var displacementData = inventory.Displacements.GetValueOrDefault(slot);

// Select displacement maps
var displacementData = inventory.Displacements.GetValueOrDefault(slot); //Default unsexed map

var equipeeSex = CompOrNull<HumanoidAppearanceComponent>(equipee)?.Sex;
if (equipeeSex != null)
{
switch (equipeeSex)
{
case Sex.Male:
if (inventory.MaleDisplacements.Count > 0)
displacementData = inventory.MaleDisplacements.GetValueOrDefault(slot);
break;
case Sex.Female:
if (inventory.FemaleDisplacements.Count > 0)
displacementData = inventory.FemaleDisplacements.GetValueOrDefault(slot);
break;
}
}

// add the new layers
foreach (var (key, layerData) in ev.Layers)
Expand All @@ -292,7 +301,7 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
index = sprite.LayerMapReserveBlank(key);

if (sprite[index] is not Layer layer)
return;
continue;

// In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
if (layerData.RsiPath == null
Expand All @@ -303,78 +312,19 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
layer.SetRsi(clothingSprite.BaseRSI);
}

// Another "temporary" fix for clothing stencil masks.
// Sprite layer redactor when
// Sprite "redactor" just a week away.
if (slot == Jumpsuit)
layerData.Shader ??= "StencilDraw";

sprite.LayerSetData(index, layerData);
layer.Offset += slotDef.Offset;

if (displacementData != null)
if (displacementData is not null)
{
if (displacementData.ShaderOverride != null)
sprite.LayerSetShader(index, displacementData.ShaderOverride);

var displacementKey = $"{key}-displacement";
if (!revealedLayers.Add(displacementKey))
{
Log.Warning($"Duplicate key for clothing visuals DISPLACEMENT: {displacementKey}.");
//Checking that the state is not tied to the current race. In this case we don't need to use the displacement maps.
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
continue;
}

var displacementLayer = _serialization.CreateCopy(displacementData.Layer, notNullableOverride: true);
displacementLayer.CopyToShaderParameters!.LayerKey = key;

// Add before main layer for this item.
sprite.AddLayer(displacementLayer, index);
sprite.LayerMapSet(displacementKey, index);

revealedLayers.Add(displacementKey);
_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
}
}

RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true);
}


/// <summary>
/// Sets a sprite's gendered mask based on gender (obviously).
/// </summary>
/// <param name="sprite">Sprite to modify</param>
/// <param name="humanoid">Humanoid, to get gender from</param>
/// <param name="clothing">Clothing component, to get mask sprite type</param>
private void SetGenderedMask(EntityUid uid, SpriteComponent sprite, ClothingComponent clothing)
{
if (!sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
return;

ClothingMask mask;
string prefix;

switch (CompOrNull<HumanoidAppearanceComponent>(uid)?.Sex)
{
case Sex.Male:
mask = clothing.MaleMask;
prefix = "male_";
break;
case Sex.Female:
mask = clothing.FemaleMask;
prefix = "female_";
break;
default:
mask = clothing.UnisexMask;
prefix = "unisex_";
break;
}

sprite.LayerSetState(layer, mask switch
{
ClothingMask.NoMask => $"{prefix}none",
ClothingMask.UniformTop => $"{prefix}top",
_ => $"{prefix}full",
});
sprite.LayerSetVisible(layer, true);
}
}
65 changes: 65 additions & 0 deletions Content.Client/DisplacementMap/DisplacementMapSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Content.Shared.DisplacementMap;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Serialization.Manager;

namespace Content.Client.DisplacementMap;

public sealed class DisplacementMapSystem : EntitySystem
{
[Dependency] private readonly ISerializationManager _serialization = default!;

public bool TryAddDisplacement(DisplacementData data, SpriteComponent sprite, int index, string key, HashSet<string> revealedLayers)
{
if (data.ShaderOverride != null)
sprite.LayerSetShader(index, data.ShaderOverride);

var displacementKey = $"{key}-displacement";
if (!revealedLayers.Add(displacementKey))
{
Log.Warning($"Duplicate key for DISPLACEMENT: {displacementKey}.");
return false;
}

//allows you not to write it every time in the YML
foreach (var pair in data.SizeMaps)
{
pair.Value.CopyToShaderParameters??= new()
{
LayerKey = "dummy",
ParameterTexture = "displacementMap",
ParameterUV = "displacementUV",
};
}

if (!data.SizeMaps.ContainsKey(32))
{
Log.Error($"DISPLACEMENT: {displacementKey} don't have 32x32 default displacement map");
return false;
}

// We choose a displacement map from the possible ones, matching the size with the original layer size.
// If there is no such a map, we use a standard 32 by 32 one
var displacementDataLayer = data.SizeMaps[EyeManager.PixelsPerMeter];
var actualRSI = sprite.LayerGetActualRSI(index);
if (actualRSI is not null)
{
if (actualRSI.Size.X != actualRSI.Size.Y)
Log.Warning($"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked");

var layerSize = actualRSI.Size.X;
if (data.SizeMaps.ContainsKey(layerSize))
displacementDataLayer = data.SizeMaps[layerSize];
}

var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true);
displacementLayer.CopyToShaderParameters!.LayerKey = key;

sprite.AddLayer(displacementLayer, index);
sprite.LayerMapSet(displacementKey, index);

revealedLayers.Add(displacementKey);

return true;
}
}
6 changes: 6 additions & 0 deletions Content.Client/Hands/Systems/HandsSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.DisplacementMap;
using Content.Client.Examine;
using Content.Client.Strip;
using Content.Client.Verbs.UI;
Expand Down Expand Up @@ -28,6 +29,7 @@ public sealed class HandsSystem : SharedHandsSystem
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly StrippableSystem _stripSys = default!;
[Dependency] private readonly ExamineSystem _examine = default!;
[Dependency] private readonly DisplacementMapSystem _displacement = default!;

public event Action<string, HandLocation>? OnPlayerAddHand;
public event Action<string>? OnPlayerRemoveHand;
Expand Down Expand Up @@ -345,6 +347,10 @@ private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsCo
}

sprite.LayerSetData(index, layerData);

//Add displacement maps
if (handComp.HandDisplacement is not null)
_displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers);
}

RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
Expand Down
12 changes: 0 additions & 12 deletions Content.Shared/Clothing/Components/ClothingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,6 @@ public sealed partial class ClothingComponent : Component
[DataField("sprite")]
public string? RsiPath;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("maleMask")]
public ClothingMask MaleMask = ClothingMask.UniformFull;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("femaleMask")]
public ClothingMask FemaleMask = ClothingMask.UniformFull;

[ViewVariables(VVAccess.ReadWrite)]
[DataField("unisexMask")]
public ClothingMask UnisexMask = ClothingMask.UniformFull;

/// <summary>
/// Name of the inventory slot the clothing is in.
/// </summary>
Expand Down
1 change: 0 additions & 1 deletion Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ public void CopyVisuals(EntityUid uid, ClothingComponent otherClothing, Clothing
clothing.ClothingVisuals = otherClothing.ClothingVisuals;
clothing.EquippedPrefix = otherClothing.EquippedPrefix;
clothing.RsiPath = otherClothing.RsiPath;
clothing.FemaleMask = otherClothing.FemaleMask;

_itemSys.VisualsChanged(uid);
Dirty(uid, clothing);
Expand Down
14 changes: 14 additions & 0 deletions Content.Shared/DisplacementMap/DisplacementData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Content.Shared.DisplacementMap;

[DataDefinition]
public sealed partial class DisplacementData
{
/// <summary>
/// allows you to attach different maps for layers of different sizes.
/// </summary>
[DataField(required: true)]
public Dictionary<int, PrototypeLayerData> SizeMaps = new();

[DataField]
public string? ShaderOverride = "DisplacedStencilDraw";
}
4 changes: 4 additions & 0 deletions Content.Shared/Hands/Components/HandsComponent.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Content.Shared.DisplacementMap;
using Content.Shared.Hands.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
Expand Down Expand Up @@ -76,6 +77,9 @@ public sealed partial class HandsComponent : Component
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f);

[DataField]
public DisplacementData? HandDisplacement;
}

[Serializable, NetSerializable]
Expand Down
26 changes: 15 additions & 11 deletions Content.Shared/Inventory/InventoryComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Robust.Shared.Containers;
using Content.Shared.DisplacementMap;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

Expand All @@ -13,18 +14,21 @@ public sealed partial class InventoryComponent : Component

[DataField("speciesId")] public string? SpeciesId { get; set; }

[DataField] public Dictionary<string, SlotDisplacementData> Displacements = [];

public SlotDefinition[] Slots = Array.Empty<SlotDefinition>();
public ContainerSlot[] Containers = Array.Empty<ContainerSlot>();

[DataDefinition]
public sealed partial class SlotDisplacementData
{
[DataField(required: true)]
public PrototypeLayerData Layer = default!;
[DataField]
public Dictionary<string, DisplacementData> Displacements = new();

/// <summary>
/// Alternate displacement maps, which if available, will be selected for the player of the appropriate gender.
/// </summary>
[DataField]
public Dictionary<string, DisplacementData> FemaleDisplacements = new();

[DataField]
public string? ShaderOverride = "DisplacedStencilDraw";
}
/// <summary>
/// Alternate displacement maps, which if available, will be selected for the player of the appropriate gender.
/// </summary>
[DataField]
public Dictionary<string, DisplacementData> MaleDisplacements = new();
}
2 changes: 0 additions & 2 deletions Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@
sprite: Clothing/Uniforms/Jumpsuit/clown.rsi
- type: Clothing
sprite: Clothing/Uniforms/Jumpsuit/clown.rsi
femaleMask: UniformTop
- type: Tag
tags:
- ClownSuit
Expand All @@ -224,7 +223,6 @@
sprite: Clothing/Uniforms/Jumpsuit/clown_banana.rsi
- type: Clothing
sprite: Clothing/Uniforms/Jumpsuit/clown_banana.rsi
femaleMask: UniformTop
- type: Construction
graph: BananaClownJumpsuit
node: jumpsuit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
- state: mask_null
map: [ "overlay" ]
- type: Clothing
femaleMask: UniformTop
maleMask: UniformTop
sprite: Clothing/Uniforms/procedural.rsi
clothingVisuals:
jumpsuit:
Expand Down
Loading

0 comments on commit 918709c

Please sign in to comment.