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:
@@ -162,7 +162,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
|
||||
var state = $"equipped-{correctedSlot}";
|
||||
|
||||
if (clothing.EquippedPrefix != null)
|
||||
if (!string.IsNullOrEmpty(clothing.EquippedPrefix))
|
||||
state = $"{clothing.EquippedPrefix}-equipped-{correctedSlot}";
|
||||
|
||||
if (clothing.EquippedState != null)
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -48,7 +49,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
}
|
||||
|
||||
private static bool IsHidden(HumanoidAppearanceComponent humanoid, HumanoidVisualLayers layer)
|
||||
=> humanoid.HiddenLayers.Contains(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
=> humanoid.HiddenLayers.ContainsKey(layer) || humanoid.PermanentlyHidden.Contains(layer);
|
||||
|
||||
private void UpdateLayers(HumanoidAppearanceComponent component, SpriteComponent sprite)
|
||||
{
|
||||
@@ -203,7 +204,7 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
|
||||
humanoid.MarkingSet = markings;
|
||||
humanoid.PermanentlyHidden = new HashSet<HumanoidVisualLayers>();
|
||||
humanoid.HiddenLayers = new HashSet<HumanoidVisualLayers>();
|
||||
humanoid.HiddenLayers = new Dictionary<HumanoidVisualLayers, SlotFlags>();
|
||||
humanoid.CustomBaseLayers = customBaseLayers;
|
||||
humanoid.Sex = profile.Sex;
|
||||
humanoid.Gender = profile.Gender;
|
||||
@@ -391,23 +392,21 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetLayerVisibility(
|
||||
EntityUid uid,
|
||||
HumanoidAppearanceComponent humanoid,
|
||||
public override void SetLayerVisibility(
|
||||
Entity<HumanoidAppearanceComponent> ent,
|
||||
HumanoidVisualLayers layer,
|
||||
bool visible,
|
||||
bool permanent,
|
||||
SlotFlags? slot,
|
||||
ref bool dirty)
|
||||
{
|
||||
base.SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty);
|
||||
base.SetLayerVisibility(ent, layer, visible, slot, ref dirty);
|
||||
|
||||
var sprite = Comp<SpriteComponent>(uid);
|
||||
var sprite = Comp<SpriteComponent>(ent);
|
||||
if (!sprite.LayerMapTryGet(layer, out var index))
|
||||
{
|
||||
if (!visible)
|
||||
return;
|
||||
else
|
||||
index = sprite.LayerMapReserveBlank(layer);
|
||||
index = sprite.LayerMapReserveBlank(layer);
|
||||
}
|
||||
|
||||
var spriteLayer = sprite[index];
|
||||
@@ -417,13 +416,14 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
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 humanoid.MarkingSet.Markings.Values)
|
||||
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, humanoid, sprite);
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, ent, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,15 +65,11 @@ public sealed class BodySystem : SharedBodySystem
|
||||
// TODO: Predict this probably.
|
||||
base.AddPart(bodyEnt, partEnt, slotId);
|
||||
|
||||
if (TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
|
||||
var layer = partEnt.Comp.ToHumanoidLayers();
|
||||
if (layer != null)
|
||||
{
|
||||
var layer = partEnt.Comp.ToHumanoidLayers();
|
||||
if (layer != null)
|
||||
{
|
||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||
_humanoidSystem.SetLayersVisibility(
|
||||
bodyEnt, layers, visible: true, permanent: true, humanoid);
|
||||
}
|
||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||
_humanoidSystem.SetLayersVisibility(bodyEnt.Owner, layers, visible: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,8 +89,7 @@ public sealed class BodySystem : SharedBodySystem
|
||||
return;
|
||||
|
||||
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
|
||||
_humanoidSystem.SetLayersVisibility(
|
||||
bodyEnt, layers, visible: false, permanent: true, humanoid);
|
||||
_humanoidSystem.SetLayersVisibility((bodyEnt, humanoid), layers, visible: false);
|
||||
}
|
||||
|
||||
public override HashSet<EntityUid> GibBody(
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class LungSystem : EntitySystem
|
||||
|
||||
private void OnMaskToggled(Entity<BreathToolComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
if (args.IsToggled || args.IsEquip)
|
||||
if (args.Mask.Comp.IsToggled)
|
||||
{
|
||||
_atmos.DisconnectInternals(ent);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public sealed class LungSystem : EntitySystem
|
||||
if (TryComp(args.Wearer, out InternalsComponent? internals))
|
||||
{
|
||||
ent.Comp.ConnectedInternalsEntity = args.Wearer;
|
||||
_internals.ConnectBreathTool((args.Wearer, internals), ent);
|
||||
_internals.ConnectBreathTool((args.Wearer.Value, internals), ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,6 @@ public sealed class IngestionBlockerSystem : EntitySystem
|
||||
|
||||
private void OnBlockerMaskToggled(Entity<IngestionBlockerComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
ent.Comp.Enabled = !args.IsToggled;
|
||||
ent.Comp.Enabled = !args.Mask.Comp.IsToggled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,13 +65,13 @@ public sealed partial class ToggleMaskEvent : InstantActionEvent { }
|
||||
/// Event raised on the mask entity when it is toggled.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct ItemMaskToggledEvent(EntityUid Wearer, string? equippedPrefix, bool IsToggled, bool IsEquip);
|
||||
public readonly record struct ItemMaskToggledEvent(Entity<MaskComponent> Mask, EntityUid? Wearer);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on the entity wearing the mask when it is toggled.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct WearerMaskToggledEvent(bool IsToggled);
|
||||
public readonly record struct WearerMaskToggledEvent(Entity<MaskComponent> Mask);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the clothing entity when it is equipped to a valid slot,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Inventory;
|
||||
@@ -28,8 +29,15 @@ public sealed partial class ClothingComponent : Component
|
||||
[DataField("quickEquip")]
|
||||
public bool QuickEquip = true;
|
||||
|
||||
/// <summary>
|
||||
/// The slots in which the clothing is considered "worn" or "equipped". E.g., putting shoes in your pockets does not
|
||||
/// equip them as far as clothing related events are concerned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this may be a combination of different slot flags, not a singular bit.
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("slots", required: true)]
|
||||
[DataField(required: true)]
|
||||
[Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)]
|
||||
public SlotFlags Slots = SlotFlags.NONE;
|
||||
|
||||
@@ -60,9 +68,25 @@ public sealed partial class ClothingComponent : Component
|
||||
public string? RsiPath;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the inventory slot the clothing is in.
|
||||
/// Name of the inventory slot the clothing is currently in.
|
||||
/// Note that this being non-null does not mean the clothing is considered "worn" or "equipped" unless the slot
|
||||
/// satisfies the <see cref="Slots"/> flags.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? InSlot;
|
||||
// TODO CLOTHING
|
||||
// Maybe keep this null unless its in a valid slot?
|
||||
// To lazy to figure out ATM if that would break anything.
|
||||
// And when doing this, combine InSlot and InSlotFlag, as it'd be a breaking change for downstreams anyway
|
||||
|
||||
/// <summary>
|
||||
/// Slot flags of the slot the clothing is currently in. See also <see cref="InSlot"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SlotFlags? InSlotFlag;
|
||||
// TODO CLOTHING
|
||||
// Maybe keep this null unless its in a valid slot?
|
||||
// And when doing this, combine InSlot and InSlotFlag, as it'd be a breaking change for downstreams anyway
|
||||
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan EquipDelay = TimeSpan.Zero;
|
||||
|
||||
@@ -19,7 +19,6 @@ public sealed partial class FoldableClothingComponent : Component
|
||||
[DataField]
|
||||
public SlotFlags? UnfoldedSlots;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// What equipped prefix does this have while in folded form?
|
||||
/// </summary>
|
||||
@@ -36,11 +35,11 @@ public sealed partial class FoldableClothingComponent : Component
|
||||
/// Which layers does this hide when Unfolded? See <see cref="HumanoidVisualLayers"/> and <see cref="HideLayerClothingComponent"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<HumanoidVisualLayers> UnfoldedHideLayers = new();
|
||||
public HashSet<HumanoidVisualLayers>? UnfoldedHideLayers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Which layers does this hide when folded? See <see cref="HumanoidVisualLayers"/> and <see cref="HideLayerClothingComponent"/>
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<HumanoidVisualLayers> FoldedHideLayers = new();
|
||||
public HashSet<HumanoidVisualLayers>? FoldedHideLayers = new();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Clothing.Components;
|
||||
@@ -11,10 +12,17 @@ namespace Content.Shared.Clothing.Components;
|
||||
public sealed partial class HideLayerClothingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The appearance layer to hide.
|
||||
/// The appearance layer(s) to hide. Use <see cref='Layers'>Layers</see> instead.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<HumanoidVisualLayers> Slots = new();
|
||||
[Obsolete("This attribute is deprecated, please use Layers instead.")]
|
||||
public HashSet<HumanoidVisualLayers>? Slots;
|
||||
|
||||
/// <summary>
|
||||
/// A map of the appearance layer(s) to hide, and the equipment slot that should hide them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<HumanoidVisualLayers, SlotFlags> Layers = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, the layer will only hide when the item is in a toggled state (e.g. masks)
|
||||
|
||||
@@ -8,15 +8,22 @@ namespace Content.Shared.Clothing.Components;
|
||||
[Access(typeof(MaskSystem))]
|
||||
public sealed partial class MaskComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Action for toggling a mask (e.g., pulling the mask down or putting it back up)
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntProtoId ToggleAction = "ActionToggleMask";
|
||||
|
||||
/// <summary>
|
||||
/// This mask can be toggled (pulled up/down)
|
||||
/// Action for toggling a mask (e.g., pulling the mask down or putting it back up)
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ToggleActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the mask is currently toggled (e.g., pulled down).
|
||||
/// This generally disables some of the mask's functionality.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsToggled;
|
||||
|
||||
@@ -27,13 +34,13 @@ public sealed partial class MaskComponent : Component
|
||||
public string EquippedPrefix = "up";
|
||||
|
||||
/// <summary>
|
||||
/// When <see langword="true"/> will function normally, otherwise will not react to events
|
||||
/// When <see langword="false"/>, the mask will not be toggleable.
|
||||
/// </summary>
|
||||
[DataField("enabled"), AutoNetworkedField]
|
||||
public bool IsEnabled = true;
|
||||
public bool IsToggleable = true;
|
||||
|
||||
/// <summary>
|
||||
/// When <see langword="true"/> will disable <see cref="IsEnabled"/> when folded
|
||||
/// When <see langword="true"/> will disable <see cref="IsToggleable"/> when folded
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool DisableOnFolded;
|
||||
|
||||
@@ -16,9 +16,9 @@ public abstract class ClothingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSys = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSys = default!;
|
||||
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _invSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly HideLayerClothingSystem _hideLayer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -29,7 +29,6 @@ public abstract class ClothingSystem : EntitySystem
|
||||
SubscribeLocalEvent<ClothingComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<ClothingComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<ClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
SubscribeLocalEvent<ClothingComponent, ItemMaskToggledEvent>(OnMaskToggled);
|
||||
|
||||
SubscribeLocalEvent<ClothingComponent, ClothingEquipDoAfterEvent>(OnEquipDoAfter);
|
||||
SubscribeLocalEvent<ClothingComponent, ClothingUnequipDoAfterEvent>(OnUnequipDoAfter);
|
||||
@@ -85,59 +84,19 @@ public abstract class ClothingSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleVisualLayers(EntityUid equipee, HashSet<HumanoidVisualLayers> layers, HashSet<HumanoidVisualLayers> appearanceLayers)
|
||||
{
|
||||
foreach (HumanoidVisualLayers layer in layers)
|
||||
{
|
||||
if (!appearanceLayers.Contains(layer))
|
||||
continue;
|
||||
|
||||
InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee);
|
||||
|
||||
bool shouldLayerShow = true;
|
||||
while (enumerator.NextItem(out EntityUid item, out SlotDefinition? slot))
|
||||
{
|
||||
if (TryComp(item, out HideLayerClothingComponent? comp))
|
||||
{
|
||||
if (comp.Slots.Contains(layer))
|
||||
{
|
||||
if (TryComp(item, out ClothingComponent? clothing) && clothing.Slots == slot.SlotFlags)
|
||||
{
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask))
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_humanoidSystem.SetLayerVisibility(equipee, layer, shouldLayerShow);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args)
|
||||
{
|
||||
component.InSlot = args.Slot;
|
||||
CheckEquipmentForLayerHide(args.Equipment, args.Equipee);
|
||||
component.InSlotFlag = args.SlotFlags;
|
||||
|
||||
if ((component.Slots & args.SlotFlags) != SlotFlags.NONE)
|
||||
{
|
||||
var gotEquippedEvent = new ClothingGotEquippedEvent(args.Equipee, component);
|
||||
RaiseLocalEvent(uid, ref gotEquippedEvent);
|
||||
if ((component.Slots & args.SlotFlags) == SlotFlags.NONE)
|
||||
return;
|
||||
|
||||
var didEquippedEvent = new ClothingDidEquippedEvent((uid, component));
|
||||
RaiseLocalEvent(args.Equipee, ref didEquippedEvent);
|
||||
}
|
||||
var gotEquippedEvent = new ClothingGotEquippedEvent(args.Equipee, component);
|
||||
RaiseLocalEvent(uid, ref gotEquippedEvent);
|
||||
|
||||
var didEquippedEvent = new ClothingDidEquippedEvent((uid, component));
|
||||
RaiseLocalEvent(args.Equipee, ref didEquippedEvent);
|
||||
}
|
||||
|
||||
protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
|
||||
@@ -152,7 +111,7 @@ public abstract class ClothingSystem : EntitySystem
|
||||
}
|
||||
|
||||
component.InSlot = null;
|
||||
CheckEquipmentForLayerHide(args.Equipment, args.Equipee);
|
||||
component.InSlotFlag = null;
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, ClothingComponent component, ref ComponentGetState args)
|
||||
@@ -162,21 +121,10 @@ public abstract class ClothingSystem : EntitySystem
|
||||
|
||||
private void OnHandleState(EntityUid uid, ClothingComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is ClothingComponentState state)
|
||||
{
|
||||
SetEquippedPrefix(uid, state.EquippedPrefix, component);
|
||||
if (component.InSlot != null && _containerSys.TryGetContainingContainer((uid, null, null), out var container))
|
||||
{
|
||||
CheckEquipmentForLayerHide(uid, container.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.Current is not ClothingComponentState state)
|
||||
return;
|
||||
|
||||
private void OnMaskToggled(Entity<ClothingComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
//TODO: sprites for 'pulled down' state. defaults to invisible due to no sprite with this prefix
|
||||
SetEquippedPrefix(ent, args.IsToggled ? args.equippedPrefix : null, ent);
|
||||
CheckEquipmentForLayerHide(ent.Owner, args.Wearer);
|
||||
SetEquippedPrefix(uid, state.EquippedPrefix, component);
|
||||
}
|
||||
|
||||
private void OnEquipDoAfter(Entity<ClothingComponent> ent, ref ClothingEquipDoAfterEvent args)
|
||||
@@ -200,12 +148,6 @@ public abstract class ClothingSystem : EntitySystem
|
||||
args.Additive += ent.Comp.StripDelay;
|
||||
}
|
||||
|
||||
private void CheckEquipmentForLayerHide(EntityUid equipment, EntityUid equipee)
|
||||
{
|
||||
if (TryComp(equipment, out HideLayerClothingComponent? clothesComp) && TryComp(equipee, out HumanoidAppearanceComponent? appearanceComp))
|
||||
ToggleVisualLayers(equipee, clothesComp.Slots, appearanceComp.HideLayersOnEquip);
|
||||
}
|
||||
|
||||
#region Public API
|
||||
|
||||
public void SetEquippedPrefix(EntityUid uid, string? prefix, ClothingComponent? clothing = null)
|
||||
|
||||
@@ -16,7 +16,8 @@ public sealed class FoldableClothingSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FoldableClothingComponent, FoldAttemptEvent>(OnFoldAttempt);
|
||||
SubscribeLocalEvent<FoldableClothingComponent, FoldedEvent>(OnFolded);
|
||||
SubscribeLocalEvent<FoldableClothingComponent, FoldedEvent>(OnFolded,
|
||||
after: [typeof(MaskSystem)]); // Mask system also modifies clothing / equipment RSI state prefixes.
|
||||
}
|
||||
|
||||
private void OnFoldAttempt(Entity<FoldableClothingComponent> ent, ref FoldAttemptEvent args)
|
||||
@@ -24,10 +25,19 @@ public sealed class FoldableClothingSystem : EntitySystem
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
// allow folding while equipped if allowed slots are the same:
|
||||
// e.g. flip a hat backwards while on your head
|
||||
if (_inventorySystem.TryGetContainingSlot(ent.Owner, out var slot) &&
|
||||
!ent.Comp.FoldedSlots.Equals(ent.Comp.UnfoldedSlots))
|
||||
if (!_inventorySystem.TryGetContainingSlot(ent.Owner, out var slot))
|
||||
return;
|
||||
|
||||
// Cannot fold clothing equipped to a slot if the slot becomes disallowed
|
||||
var newSlots = args.Comp.IsFolded ? ent.Comp.UnfoldedSlots : ent.Comp.FoldedSlots;
|
||||
if (newSlots != null && (newSlots.Value & slot.SlotFlags) != slot.SlotFlags)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Setting hidden layers while equipped is not currently supported.
|
||||
if (ent.Comp.FoldedHideLayers != null || ent.Comp.UnfoldedHideLayers != null)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
@@ -48,7 +58,14 @@ public sealed class FoldableClothingSystem : EntitySystem
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, ent.Comp.FoldedHeldPrefix, false, itemComp);
|
||||
|
||||
if (TryComp<HideLayerClothingComponent>(ent.Owner, out var hideLayerComp))
|
||||
// This is janky and likely to lead to bugs.
|
||||
// I.e., overriding this and resetting it again later will lead to bugs if someone tries to modify clothing
|
||||
// in yaml, but doesn't realise theres actually two other fields on an unrelated component that they also need
|
||||
// to modify.
|
||||
// This should instead work via an event or something that gets raised to optionally modify the currently hidden layers.
|
||||
// Or at the very least it should stash the old layers and restore them when unfolded.
|
||||
// TODO CLOTHING fix this.
|
||||
if (ent.Comp.FoldedHideLayers != null && TryComp<HideLayerClothingComponent>(ent.Owner, out var hideLayerComp))
|
||||
hideLayerComp.Slots = ent.Comp.FoldedHideLayers;
|
||||
|
||||
}
|
||||
@@ -63,7 +80,8 @@ public sealed class FoldableClothingSystem : EntitySystem
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, null, false, itemComp);
|
||||
|
||||
if (TryComp<HideLayerClothingComponent>(ent.Owner, out var hideLayerComp))
|
||||
// TODO CLOTHING fix this.
|
||||
if (ent.Comp.UnfoldedHideLayers != null && TryComp<HideLayerClothingComponent>(ent.Owner, out var hideLayerComp))
|
||||
hideLayerComp.Slots = ent.Comp.UnfoldedHideLayers;
|
||||
|
||||
}
|
||||
|
||||
106
Content.Shared/Clothing/EntitySystems/HideLayerClothingSystem.cs
Normal file
106
Content.Shared/Clothing/EntitySystems/HideLayerClothingSystem.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
public sealed class HideLayerClothingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoid = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<HideLayerClothingComponent, ClothingGotUnequippedEvent>(OnHideGotUnequipped);
|
||||
SubscribeLocalEvent<HideLayerClothingComponent, ClothingGotEquippedEvent>(OnHideGotEquipped);
|
||||
SubscribeLocalEvent<HideLayerClothingComponent, ItemMaskToggledEvent>(OnHideToggled);
|
||||
}
|
||||
|
||||
private void OnHideToggled(Entity<HideLayerClothingComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
if (args.Wearer != null)
|
||||
SetLayerVisibility(ent!, args.Wearer.Value, hideLayers: true);
|
||||
}
|
||||
|
||||
private void OnHideGotEquipped(Entity<HideLayerClothingComponent> ent, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
SetLayerVisibility(ent!, args.Wearer, hideLayers: true);
|
||||
}
|
||||
|
||||
private void OnHideGotUnequipped(Entity<HideLayerClothingComponent> ent, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
SetLayerVisibility(ent!, args.Wearer, hideLayers: false);
|
||||
}
|
||||
|
||||
private void SetLayerVisibility(
|
||||
Entity<HideLayerClothingComponent?, ClothingComponent?> clothing,
|
||||
Entity<HumanoidAppearanceComponent?> user,
|
||||
bool hideLayers)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (!Resolve(clothing.Owner, ref clothing.Comp1, ref clothing.Comp2))
|
||||
return;
|
||||
|
||||
if (!Resolve(user.Owner, ref user.Comp))
|
||||
return;
|
||||
|
||||
hideLayers &= IsEnabled(clothing!);
|
||||
|
||||
var hideable = user.Comp.HideLayersOnEquip;
|
||||
var inSlot = clothing.Comp2.InSlotFlag ?? SlotFlags.NONE;
|
||||
|
||||
// This method should only be getting called while the clothing is equipped (though possibly currently in
|
||||
// the process of getting unequipped).
|
||||
DebugTools.AssertNotNull(clothing.Comp2.InSlot);
|
||||
DebugTools.AssertNotNull(clothing.Comp2.InSlotFlag);
|
||||
DebugTools.AssertNotEqual(inSlot, SlotFlags.NONE);
|
||||
|
||||
var dirty = false;
|
||||
|
||||
// iterate the HideLayerClothingComponent's layers map and check that
|
||||
// the clothing is (or was)equipped in a matching slot.
|
||||
foreach (var (layer, validSlots) in clothing.Comp1.Layers)
|
||||
{
|
||||
if (!hideable.Contains(layer))
|
||||
continue;
|
||||
|
||||
// Only update this layer if we are currently equipped to the relevant slot.
|
||||
if (validSlots.HasFlag(inSlot))
|
||||
_humanoid.SetLayerVisibility(user!, layer, !hideLayers, inSlot, ref dirty);
|
||||
}
|
||||
|
||||
// Fallback for obsolete field: assume we want to hide **all** layers, as long as we are equipped to any
|
||||
// relevant clothing slot
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (clothing.Comp1.Slots is { } slots && clothing.Comp2.Slots.HasFlag(inSlot))
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
foreach (var layer in slots)
|
||||
{
|
||||
if (hideable.Contains(layer))
|
||||
_humanoid.SetLayerVisibility(user!, layer, !hideLayers, inSlot, ref dirty);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
Dirty(user!);
|
||||
}
|
||||
|
||||
private bool IsEnabled(Entity<HideLayerClothingComponent, ClothingComponent> clothing)
|
||||
{
|
||||
// TODO Generalize this
|
||||
// I.e., make this and mask component use some generic toggleable.
|
||||
|
||||
if (!clothing.Comp1.HideOnToggle)
|
||||
return true;
|
||||
|
||||
if (!TryComp(clothing, out MaskComponent? mask))
|
||||
return true;
|
||||
|
||||
return !mask.IsToggled;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Foldable;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -15,6 +14,7 @@ public sealed class MaskSystem : EntitySystem
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ClothingSystem _clothing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -35,53 +35,109 @@ public sealed class MaskSystem : EntitySystem
|
||||
private void OnToggleMask(Entity<MaskComponent> ent, ref ToggleMaskEvent args)
|
||||
{
|
||||
var (uid, mask) = ent;
|
||||
if (mask.ToggleActionEntity == null || !_timing.IsFirstTimePredicted || !mask.IsEnabled)
|
||||
if (mask.ToggleActionEntity == null || !mask.IsToggleable)
|
||||
return;
|
||||
|
||||
if (!_inventorySystem.TryGetSlotEntity(args.Performer, "mask", out var existing) || !uid.Equals(existing))
|
||||
return;
|
||||
// Masks are currently only toggleable via the action while equipped.
|
||||
// Its possible this might change in future?
|
||||
|
||||
mask.IsToggled ^= true;
|
||||
// TODO Inventory / Clothing
|
||||
// Add an easier way to check if clothing is equipped to a valid slot.
|
||||
if (!TryComp(ent, out ClothingComponent? clothing)
|
||||
|| clothing.InSlotFlag is not { } slotFlag
|
||||
|| !clothing.Slots.HasFlag(slotFlag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetToggled((uid, mask), !mask.IsToggled);
|
||||
|
||||
var dir = mask.IsToggled ? "down" : "up";
|
||||
var msg = $"action-mask-pull-{dir}-popup-message";
|
||||
_popupSystem.PopupClient(Loc.GetString(msg, ("mask", uid)), args.Performer, args.Performer);
|
||||
|
||||
ToggleMaskComponents(uid, mask, args.Performer, mask.EquippedPrefix);
|
||||
}
|
||||
|
||||
// set to untoggled when unequipped, so it isn't left in a 'pulled down' state
|
||||
private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args)
|
||||
{
|
||||
if (!mask.IsToggled || !mask.IsEnabled)
|
||||
return;
|
||||
|
||||
mask.IsToggled = false;
|
||||
ToggleMaskComponents(uid, mask, args.Equipee, mask.EquippedPrefix, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after setting IsToggled, raises events and dirties.
|
||||
/// <summary>
|
||||
private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, string? equippedPrefix = null, bool isEquip = false)
|
||||
{
|
||||
Dirty(uid, mask);
|
||||
if (mask.ToggleActionEntity is {} action)
|
||||
_actionSystem.SetToggled(action, mask.IsToggled);
|
||||
|
||||
var maskEv = new ItemMaskToggledEvent(wearer, equippedPrefix, mask.IsToggled, isEquip);
|
||||
RaiseLocalEvent(uid, ref maskEv);
|
||||
|
||||
var wearerEv = new WearerMaskToggledEvent(mask.IsToggled);
|
||||
RaiseLocalEvent(wearer, ref wearerEv);
|
||||
// Masks are currently always un-toggled when unequipped.
|
||||
SetToggled((uid, mask), false);
|
||||
}
|
||||
|
||||
private void OnFolded(Entity<MaskComponent> ent, ref FoldedEvent args)
|
||||
{
|
||||
if (ent.Comp.DisableOnFolded)
|
||||
ent.Comp.IsEnabled = !args.IsFolded;
|
||||
ent.Comp.IsToggled = args.IsFolded;
|
||||
// See FoldableClothingComponent
|
||||
|
||||
ToggleMaskComponents(ent.Owner, ent.Comp, ent.Owner);
|
||||
if (!ent.Comp.DisableOnFolded)
|
||||
return;
|
||||
|
||||
// While folded, we force the mask to be toggled / pulled down, so that its functionality as a mask is disabled,
|
||||
// and we also prevent it from being un-toggled. We also automatically untoggle it when it gets unfolded, so it
|
||||
// fully returns to its previous state when folded & unfolded.
|
||||
|
||||
SetToggled(ent!, args.IsFolded, force: true);
|
||||
SetToggleable(ent!, !args.IsFolded);
|
||||
}
|
||||
|
||||
public void SetToggled(Entity<MaskComponent?> mask, bool toggled, bool force = false)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (!Resolve(mask.Owner, ref mask.Comp))
|
||||
return;
|
||||
|
||||
if (!force && !mask.Comp.IsToggleable)
|
||||
return;
|
||||
|
||||
if (mask.Comp.IsToggled == toggled)
|
||||
return;
|
||||
|
||||
mask.Comp.IsToggled = toggled;
|
||||
|
||||
if (mask.Comp.ToggleActionEntity is { } action)
|
||||
_actionSystem.SetToggled(action, mask.Comp.IsToggled);
|
||||
|
||||
// TODO Generalize toggling & clothing prefixes. See also FoldableClothingComponent
|
||||
var prefix = mask.Comp.IsToggled ? mask.Comp.EquippedPrefix : null;
|
||||
_clothing.SetEquippedPrefix(mask, prefix);
|
||||
|
||||
// TODO Inventory / Clothing
|
||||
// Add an easier way to get the entity that is wearing clothing in a valid slot.
|
||||
EntityUid? wearer = null;
|
||||
if (TryComp(mask, out ClothingComponent? clothing)
|
||||
&& clothing.InSlotFlag is {} slotFlag
|
||||
&& clothing.Slots.HasFlag(slotFlag))
|
||||
{
|
||||
wearer = Transform(mask).ParentUid;
|
||||
}
|
||||
|
||||
var maskEv = new ItemMaskToggledEvent(mask!, wearer);
|
||||
RaiseLocalEvent(mask, ref maskEv);
|
||||
|
||||
if (wearer != null)
|
||||
{
|
||||
var wearerEv = new WearerMaskToggledEvent(mask!);
|
||||
RaiseLocalEvent(wearer.Value, ref wearerEv);
|
||||
}
|
||||
|
||||
Dirty(mask);
|
||||
}
|
||||
|
||||
public void SetToggleable(Entity<MaskComponent?> mask, bool toggleable)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
|
||||
if (!Resolve(mask.Owner, ref mask.Comp))
|
||||
return;
|
||||
|
||||
if (mask.Comp.IsToggleable == toggleable)
|
||||
return;
|
||||
|
||||
if (mask.Comp.ToggleActionEntity is { } action)
|
||||
_actionSystem.SetEnabled(action, mask.Comp.IsToggleable);
|
||||
|
||||
mask.Comp.IsToggleable = toggleable;
|
||||
Dirty(mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public sealed class FoldableSystem : EntitySystem
|
||||
if (_container.IsEntityInContainer(uid) && !fold.CanFoldInsideContainer)
|
||||
return false;
|
||||
|
||||
var ev = new FoldAttemptEvent();
|
||||
var ev = new FoldAttemptEvent(fold);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
return !ev.Cancelled;
|
||||
}
|
||||
@@ -157,7 +157,7 @@ public sealed class FoldableSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="Cancelled"></param>
|
||||
[ByRefEvent]
|
||||
public record struct FoldAttemptEvent(bool Cancelled = false);
|
||||
public record struct FoldAttemptEvent(FoldableComponent Comp, bool Cancelled = false);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on an entity after it has been folded.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -59,11 +60,12 @@ public sealed partial class HumanoidAppearanceComponent : Component
|
||||
public Color SkinColor { get; set; } = Color.FromHex("#C0967F");
|
||||
|
||||
/// <summary>
|
||||
/// Visual layers currently hidden. This will affect the base sprite
|
||||
/// on this humanoid layer, and any markings that sit above it.
|
||||
/// A map of the visual layers currently hidden to the equipment
|
||||
/// slots that are currently hiding them. This will affect the base
|
||||
/// sprite on this humanoid layer, and any markings that sit above it.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<HumanoidVisualLayers> HiddenLayers = new();
|
||||
public Dictionary<HumanoidVisualLayers, SlotFlags> HiddenLayers = new();
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public Sex Sex = Sex.Male;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public abstract class SharedIdentitySystem : EntitySystem
|
||||
|
||||
private void OnMaskToggled(Entity<IdentityBlockerComponent> ent, ref ItemMaskToggledEvent args)
|
||||
{
|
||||
ent.Comp.Enabled = !args.IsToggled;
|
||||
ent.Comp.Enabled = !args.Mask.Comp.IsToggled;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- type: Foldable
|
||||
canFoldInsideContainer: true
|
||||
- type: FoldableClothing
|
||||
foldedEquippedPrefix: "" # folding the bandana will toggles the mask, which adds the toggled prefix. This overrides that prefix.
|
||||
foldedSlots:
|
||||
- HEAD
|
||||
unfoldedSlots:
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
- HamsterWearable
|
||||
- WhitelistChameleon
|
||||
- type: HideLayerClothing
|
||||
slots:
|
||||
- Snout
|
||||
layers:
|
||||
Snout: Mask
|
||||
hideOnToggle: true
|
||||
|
||||
- type: entity
|
||||
|
||||
Reference in New Issue
Block a user