diff --git a/Content.Shared/Clothing/Components/HideLayerClothingComponent.cs b/Content.Shared/Clothing/Components/HideLayerClothingComponent.cs
new file mode 100644
index 0000000000..ac3d9b9789
--- /dev/null
+++ b/Content.Shared/Clothing/Components/HideLayerClothingComponent.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Humanoid;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Clothing.Components;
+
+///
+/// This is used for a clothing item that hides an appearance layer.
+/// The entity's HumanoidAppearance component must have the corresponding hideLayerOnEquip value.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class HideLayerClothingComponent : Component
+{
+ ///
+ /// The appearance layer to hide.
+ ///
+ [DataField]
+ public HashSet Slots = new();
+
+ ///
+ /// If true, the layer will only hide when the item is in a toggled state (e.g. masks)
+ ///
+ [DataField]
+ public bool HideOnToggle = false;
+}
diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
index 85df04d20a..427d83cc3c 100644
--- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
+++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
@@ -6,26 +6,19 @@ using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
-using Content.Shared.Tag;
+using Robust.Shared.Containers;
using Robust.Shared.GameStates;
-using System.Linq;
namespace Content.Shared.Clothing.EntitySystems;
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 TagSystem _tagSystem = default!;
[Dependency] private readonly InventorySystem _invSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
- [ValidatePrototypeId]
- private const string HairTag = "HidesHair";
-
- [ValidatePrototypeId]
- private const string NoseTag = "HidesNose";
-
public override void Initialize()
{
base.Initialize();
@@ -89,18 +82,22 @@ public abstract class ClothingSystem : EntitySystem
}
}
- private void ToggleVisualLayer(EntityUid equipee, HumanoidVisualLayers layer, string tag)
+ private void ToggleVisualLayers(EntityUid equipee, HashSet layers, HashSet appearanceLayers)
{
- InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee, SlotFlags.HEAD ^ SlotFlags.MASK);
- bool shouldLayerShow = true;
-
- while (enumerator.NextItem(out EntityUid item))
+ foreach (HumanoidVisualLayers layer in layers)
{
- if (_tagSystem.HasTag(item, tag))
+ if (!appearanceLayers.Contains(layer))
+ break;
+
+ InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee);
+
+ bool shouldLayerShow = true;
+ while (enumerator.NextItem(out EntityUid item))
{
- if (tag == NoseTag) //Special check needs to be made for NoseTag, due to masks being toggleable
+ if (TryComp(item, out HideLayerClothingComponent? comp))
{
- if (TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing))
+ //Checks for mask toggling. TODO: Make a generic system for this
+ if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing))
{
if (clothing.EquippedPrefix != mask.EquippedPrefix)
{
@@ -114,50 +111,49 @@ public abstract class ClothingSystem : EntitySystem
break;
}
}
- else
- {
- shouldLayerShow = false;
- break;
- }
}
+ _humanoidSystem.SetLayerVisibility(equipee, layer, shouldLayerShow);
}
- _humanoidSystem.SetLayerVisibility(equipee, layer, shouldLayerShow);
}
protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args)
{
component.InSlot = args.Slot;
- if ((new string[] { "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, HairTag))
- ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Hair, HairTag);
- if ((new string[] { "mask", "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, NoseTag))
- ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Snout, NoseTag);
+ CheckEquipmentForLayerHide(args.Equipment, args.Equipee);
}
protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
{
component.InSlot = null;
- if ((new string[] { "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, HairTag))
- ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Hair, HairTag);
- if ((new string[] { "mask", "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, NoseTag))
- ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Snout, NoseTag);
+ CheckEquipmentForLayerHide(args.Equipment, args.Equipee);
}
private void OnGetState(EntityUid uid, ClothingComponent component, ref ComponentGetState args)
{
args.State = new ClothingComponentState(component.EquippedPrefix);
+ if (component.InSlot != null && _containerSys.TryGetContainingContainer(uid, out var container))
+ {
+ CheckEquipmentForLayerHide(uid, container.Owner);
+ }
}
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, out var container))
+ {
+ CheckEquipmentForLayerHide(uid, container.Owner);
+ }
+ }
}
private void OnMaskToggled(Entity 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);
- ToggleVisualLayer(args.Wearer, HumanoidVisualLayers.Snout, NoseTag);
+ CheckEquipmentForLayerHide(ent.Owner, args.Wearer);
}
private void OnEquipDoAfter(Entity ent, ref ClothingEquipDoAfterEvent args)
@@ -176,6 +172,12 @@ public abstract class ClothingSystem : EntitySystem
_handsSystem.TryPickup(args.User, ent);
}
+ 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)
diff --git a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
index 82d6964522..016ab64f1a 100644
--- a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
+++ b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
@@ -82,6 +82,12 @@ public sealed partial class HumanoidAppearanceComponent : Component
///
[ViewVariables(VVAccess.ReadOnly)]
public Color? CachedFacialHairColor;
+
+ ///
+ /// Which layers of this humanoid that should be hidden on equipping a corresponding item..
+ ///
+ [DataField]
+ public HashSet HideLayersOnEquip = [HumanoidVisualLayers.Hair];
}
[DataDefinition]
diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
index d13b284ff2..e1e5ddc11f 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
@@ -133,11 +133,13 @@
unequipSound: /Audio/Mecha/mechmove03.ogg
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
- HelmetEVA
- - HidesNose
- type: IdentityBlocker
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
- type: entity
abstract: true
@@ -173,10 +175,12 @@
- type: IngestionBlocker
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
- - HidesNose
- type: IdentityBlocker
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
- type: entity
abstract: true
@@ -248,6 +252,6 @@
- type: TemperatureProtection
coefficient: 0.7
- type: GroupExamine
- - type: Tag
- tags:
- - HidesHair
+ - type: HideLayerClothing
+ slots:
+ - Hair
diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
index a35cf498f6..453f3d1267 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
@@ -17,9 +17,9 @@
sprite: Clothing/Head/Hardsuits/basic.rsi
- type: Clothing
sprite: Clothing/Head/Hardsuits/basic.rsi
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
#Atmospherics Hardsuit
- type: entity
diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml
index fba77d885f..ec0921c512 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml
@@ -382,9 +382,12 @@
sprite: Clothing/Head/Hats/plaguedoctor.rsi
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
- ClothMade
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
- type: entity
parent: ClothingHeadBase
@@ -537,9 +540,11 @@
sprite: Clothing/Head/Hats/witch.rsi
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
- ClothMade
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
index f38efd5c8b..88070b430f 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml
@@ -110,6 +110,10 @@
Blunt: 0.95
Slash: 0.95
Piercing: 0.95
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
#Janitorial Bombsuit Helmet
- type: entity
@@ -157,10 +161,13 @@
sprite: Clothing/Head/Helmets/spaceninja.rsi
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
- type: IngestionBlocker
- type: IdentityBlocker
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
#Templar Helmet
- type: entity
@@ -220,8 +227,11 @@
- type: IdentityBlocker
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
#Atmos Fire Helmet
- type: entity
@@ -244,8 +254,11 @@
- type: IdentityBlocker
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
#Chitinous Helmet
- type: entity
diff --git a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml
index 68d50c5ab1..b62834dd98 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml
@@ -13,8 +13,11 @@
- type: IngestionBlocker
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
- type: entity
parent: ClothingHeadHatHoodBioGeneral
@@ -94,9 +97,11 @@
sprite: Clothing/Head/Hoods/chaplain.rsi
- type: Tag
tags:
- - HidesHair
- HamsterWearable
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
@@ -110,8 +115,10 @@
sprite: Clothing/Head/Hoods/cult.rsi
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
@@ -125,9 +132,11 @@
sprite: Clothing/Head/Hoods/nun.rsi
- type: Tag
tags:
- - HidesHair
- HamsterWearable
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
@@ -145,9 +154,10 @@
Heat: 0.95
Radiation: 0.65
- type: BreathMask
- - type: Tag
- tags:
- - HidesHair
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
- type: entity
parent: ClothingHeadBase
@@ -161,8 +171,10 @@
sprite: Clothing/Head/Hoods/goliathcloak.rsi
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
@@ -175,9 +187,9 @@
sprite: Clothing/Head/Hoods/iansuit.rsi
- type: Clothing
sprite: Clothing/Head/Hoods/iansuit.rsi
- - type: Tag
- tags:
- - HidesHair
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
@@ -190,9 +202,9 @@
sprite: Clothing/Head/Hoods/carpsuit.rsi
- type: Clothing
sprite: Clothing/Head/Hoods/carpsuit.rsi
- - type: Tag
- tags:
- - HidesHair
+ - type: HideLayerClothing
+ slots:
+ - Hair
- type: entity
parent: ClothingHeadBase
@@ -207,8 +219,11 @@
- type: IdentityBlocker
- type: Tag
tags:
- - HidesHair
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
#Winter Coat Hoods
- type: entity
diff --git a/Resources/Prototypes/Entities/Clothing/Head/welding.yml b/Resources/Prototypes/Entities/Clothing/Head/welding.yml
index 93d9b1e084..cd5c63d7ec 100644
--- a/Resources/Prototypes/Entities/Clothing/Head/welding.yml
+++ b/Resources/Prototypes/Entities/Clothing/Head/welding.yml
@@ -19,6 +19,9 @@
- type: Tag
tags:
- WhitelistChameleon
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: WeldingMaskBase
diff --git a/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml b/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml
index 246b47b800..f5ad2fb6c8 100644
--- a/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml
+++ b/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml
@@ -25,7 +25,10 @@
- type: Tag
tags:
- Bandana
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
+ hideOnToggle: true
- type: entity
parent: ClothingMaskBandanaBase
diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml
index 4d7351464f..850050b2d3 100644
--- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml
+++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml
@@ -15,7 +15,10 @@
tags:
- HamsterWearable
- WhitelistChameleon
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
+ hideOnToggle: true
- type: entity
parent: ClothingMaskGas
@@ -197,7 +200,9 @@
tags:
- ClownMask
- WhitelistChameleon
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskClownBase
@@ -208,7 +213,9 @@
- ClownMask
- HamsterWearable
- WhitelistChameleon
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskClown
@@ -235,9 +242,9 @@
sprite: Clothing/Mask/joy.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
@@ -255,7 +262,9 @@
tags:
- HamsterWearable
- WhitelistChameleon
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskPullableBase
@@ -306,9 +315,10 @@
- type: BreathMask
- type: IngestionBlocker
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
+ hideOnToggle: true
- type: entity
parent: ClothingMaskClownBase
@@ -338,8 +348,11 @@
- type: Tag
tags:
- WhitelistChameleon
- - HidesHair
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
+ hideOnToggle: true
- type: entity
parent: ClothingMaskGasExplorer
@@ -365,8 +378,11 @@
- type: Tag
tags:
- WhitelistChameleon
- - HidesHair
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Hair
+ - Snout
+ hideOnToggle: true
- type: entity
parent: ClothingMaskGasERT
@@ -401,7 +417,9 @@
tags:
- HamsterWearable
- WhitelistChameleon
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: IdentityBlocker
- type: entity
@@ -416,9 +434,9 @@
sprite: Clothing/Mask/fox.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
@@ -432,9 +450,9 @@
sprite: Clothing/Mask/bee.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
@@ -448,9 +466,9 @@
sprite: Clothing/Mask/bear.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
@@ -464,9 +482,9 @@
sprite: Clothing/Mask/raven.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
@@ -480,9 +498,9 @@
sprite: Clothing/Mask/jackal.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
@@ -496,9 +514,9 @@
sprite: Clothing/Mask/bat.rsi
- type: BreathMask
- type: IdentityBlocker
- - type: Tag
- tags:
- - HidesNose
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskBase
diff --git a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml
index d0e4e4d7e9..0a0fc68469 100644
--- a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml
+++ b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml
@@ -6,8 +6,7 @@
suffix: Chameleon
components:
- type: Tag
- tags: # ignore "WhitelistChameleon" tag
- - HidesNose
+ tags: [] # ignore "WhitelistChameleon" tag
- type: Sprite
sprite: Clothing/Mask/gas.rsi
- type: Clothing
@@ -21,6 +20,9 @@
interfaces:
- key: enum.ChameleonUiKey.Key
type: ChameleonBoundUserInterface
+ - type: HideLayerClothing
+ slots:
+ - Snout
- type: entity
parent: ClothingMaskGasChameleon
@@ -28,3 +30,6 @@
suffix: Voice Mask, Chameleon
components:
- type: VoiceMasker
+ - type: HideLayerClothing
+ slots:
+ - Snout
diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
index 2c0ab1e15d..5a54b56c48 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
@@ -51,6 +51,11 @@
accent: dwarf
- type: Speech
speechSounds: Bass
+ - type: HumanoidAppearance
+ species: Human
+ hideLayersOnEquip:
+ - Hair
+ - Snout
- type: entity
parent: BaseSpeciesDummy
diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml
index d469d6c60f..6716d3902b 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/human.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml
@@ -15,6 +15,11 @@
spawned:
- id: FoodMeatHuman
amount: 5
+ - type: HumanoidAppearance
+ species: Human
+ hideLayersOnEquip:
+ - Hair
+ - Snout
- type: entity
parent: BaseSpeciesDummy