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