Fix broken layer hiding on clothes with multiple equipment slots (#34080)

* Fix broken layer hiding on clothes with multiple equipment slots

* Refactor ToggleVisualLayers, HideLayerClothingComponent, and ClothingComponent to allow more
precise layer hide behavior and more CPU efficient layer toggling.

* Adjust HumanoidAppearaceSystem to track which slots are hiding a given layer (e.g. gas mask and welding mask)
Add documentation
Change gas masks to use the new HideLayerClothingComponent structure as an example of its usage

* Fix the delayed snout bug

* Misc cleanup

* Make `bool permanent` implicit from SlotFlags

any non-permanent visibility toggle with `SlotFlags.None` isn't supported with how its set up. And similarly, the slot flags argument does nothing if permanent = true. So IMO it makes more sense to infer it from a nullable arg.

* Split into separate system

Too much pasta

* Remove (hopefully unnecessary) refresh

* Fisk mask networking

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

* Keep old behaviour, use clearer names?

I'm just guessing at what this was meant to do

* english

* Separate slot name & flag

* dirty = true

* fix comment

* Improved SetLayerVisibility with dirtying logic suggested by @ElectroJr

* Only set mask toggled if DisableOnFold is true

* FoldableClothingSystem fixes

* fix bandana state

* Better comment

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
This commit is contained in:
paige404
2025-03-20 09:30:47 -04:00
committed by GitHub
parent 65f393bb14
commit 2e7f01b99e
20 changed files with 370 additions and 184 deletions

View File

@@ -1,11 +1,13 @@
using System.IO;
using System.Linq;
using System.Numerics;
using Content.Shared.CCVar;
using Content.Shared.Decals;
using Content.Shared.Examine;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -114,22 +116,22 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
/// <summary>
/// Toggles a humanoid's sprite layer visibility.
/// </summary>
/// <param name="uid">Humanoid mob's UID</param>
/// <param name="ent">Humanoid entity</param>
/// <param name="layer">Layer to toggle visibility for</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void SetLayerVisibility(EntityUid uid,
/// <param name="visible">Whether to hide or show the layer. If more than once piece of clothing is hiding the layer, it may remain hidden.</param>
/// <param name="source">Equipment slot that has the clothing that is (or was) hiding the layer. If not specified, the change is "permanent" (i.e., see <see cref="HumanoidAppearanceComponent.PermanentlyHidden"/>)</param>
public void SetLayerVisibility(Entity<HumanoidAppearanceComponent?> ent,
HumanoidVisualLayers layer,
bool visible,
bool permanent = false,
HumanoidAppearanceComponent? humanoid = null)
SlotFlags? source = null)
{
if (!Resolve(uid, ref humanoid, false))
if (!Resolve(ent.Owner, ref ent.Comp, false))
return;
var dirty = false;
SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
SetLayerVisibility(ent!, layer, visible, source, ref dirty);
if (dirty)
Dirty(uid, humanoid);
Dirty(ent);
}
/// <summary>
@@ -163,49 +165,75 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
/// <summary>
/// Sets the visibility for multiple layers at once on a humanoid's sprite.
/// </summary>
/// <param name="uid">Humanoid mob's UID</param>
/// <param name="ent">Humanoid entity</param>
/// <param name="layers">An enumerable of all sprite layers that are going to have their visibility set</param>
/// <param name="visible">The visibility state of the layers given</param>
/// <param name="permanent">If this is a permanent change, or temporary. Permanent layers are stored in their own hash set.</param>
/// <param name="humanoid">Humanoid component of the entity</param>
public void SetLayersVisibility(EntityUid uid, IEnumerable<HumanoidVisualLayers> layers, bool visible, bool permanent = false,
HumanoidAppearanceComponent? humanoid = null)
public void SetLayersVisibility(Entity<HumanoidAppearanceComponent?> ent,
IEnumerable<HumanoidVisualLayers> layers,
bool visible)
{
if (!Resolve(uid, ref humanoid))
if (!Resolve(ent.Owner, ref ent.Comp, false))
return;
var dirty = false;
foreach (var layer in layers)
{
SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
SetLayerVisibility(ent!, layer, visible, null, ref dirty);
}
if (dirty)
Dirty(uid, humanoid);
Dirty(ent);
}
protected virtual void SetLayerVisibility(
EntityUid uid,
HumanoidAppearanceComponent humanoid,
/// <inheritdoc cref="SetLayerVisibility(Entity{HumanoidAppearanceComponent?},HumanoidVisualLayers,bool,Nullable{SlotFlags})"/>
public virtual void SetLayerVisibility(
Entity<HumanoidAppearanceComponent> ent,
HumanoidVisualLayers layer,
bool visible,
bool permanent,
SlotFlags? source,
ref bool dirty)
{
#if DEBUG
if (source is {} s)
{
DebugTools.AssertNotEqual(s, SlotFlags.NONE);
// Check that only a single bit in the bitflag is set
var powerOfTwo = BitOperations.RoundUpToPowerOf2((uint)s);
DebugTools.AssertEqual((uint)s, powerOfTwo);
}
#endif
if (visible)
{
if (permanent)
dirty |= humanoid.PermanentlyHidden.Remove(layer);
if (source is not {} slot)
{
dirty |= ent.Comp.PermanentlyHidden.Remove(layer);
}
else if (ent.Comp.HiddenLayers.TryGetValue(layer, out var oldSlots))
{
// This layer might be getting hidden by more than one piece of equipped clothing.
// remove slot flag from the set of slots hiding this layer, then check if there are any left.
ent.Comp.HiddenLayers[layer] = ~slot & oldSlots;
if (ent.Comp.HiddenLayers[layer] == SlotFlags.NONE)
ent.Comp.HiddenLayers.Remove(layer);
dirty |= humanoid.HiddenLayers.Remove(layer);
dirty |= (oldSlots & slot) != 0;
}
}
else
{
if (permanent)
dirty |= humanoid.PermanentlyHidden.Add(layer);
if (source is not { } slot)
{
dirty |= ent.Comp.PermanentlyHidden.Add(layer);
}
else
{
var oldSlots = ent.Comp.HiddenLayers.GetValueOrDefault(layer);
ent.Comp.HiddenLayers[layer] = slot | oldSlots;
dirty |= (oldSlots & slot) != slot;
}
dirty |= humanoid.HiddenLayers.Add(layer);
}
}