diff --git a/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs b/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs index 57ffb68a17..9e9a08e32f 100644 --- a/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs +++ b/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs @@ -4,6 +4,7 @@ using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Items; using Robust.Client.Graphics; using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Clothing @@ -12,6 +13,7 @@ namespace Content.Client.GameObjects.Components.Clothing [ComponentReference(typeof(ItemComponent))] public class ClothingComponent : ItemComponent { + private FemaleClothingMask _femaleMask; public override string Name => "Clothing"; public override uint? NetID => ContentNetIDs.CLOTHING; public override Type StateType => typeof(ClothingComponentState); @@ -19,6 +21,20 @@ namespace Content.Client.GameObjects.Components.Clothing [ViewVariables(VVAccess.ReadWrite)] public string ClothingEquippedPrefix { get; set; } + [ViewVariables(VVAccess.ReadWrite)] + public FemaleClothingMask FemaleMask + { + get => _femaleMask; + set => _femaleMask = value; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _femaleMask, "femaleMask", FemaleClothingMask.UniformFull); + } + public (RSI rsi, RSI.StateId stateId)? GetEquippedStateInfo(EquipmentSlotDefines.SlotFlags slot) { if (RsiPath == null) @@ -47,4 +63,11 @@ namespace Content.Client.GameObjects.Components.Clothing EquippedPrefix = clothingComponentState.EquippedPrefix; } } + + public enum FemaleClothingMask + { + NoMask = 0, + UniformFull, + UniformTop + } } diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs index 853a048a92..d0290052eb 100644 --- a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs +++ b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Content.Client.GameObjects.Components.Clothing; using Content.Shared.GameObjects; +using Content.Shared.Preferences.Appearance; using Robust.Client.GameObjects; using Robust.Client.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; @@ -14,8 +15,6 @@ using Robust.Shared.IoC; using Robust.Shared.ViewVariables; using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines; using static Content.Shared.GameObjects.SharedInventoryComponent.ClientInventoryMessage; -using Content.Shared.GameObjects.Components.Inventory; -using System; namespace Content.Client.GameObjects { @@ -130,6 +129,17 @@ namespace Content.Client.GameObjects _sprite.LayerSetVisible(slot, true); _sprite.LayerSetRSI(slot, rsi); _sprite.LayerSetState(slot, state); + + if (slot == Slots.INNERCLOTHING) + { + _sprite.LayerSetState(HumanoidVisualLayers.StencilMask, clothing.FemaleMask switch + { + FemaleClothingMask.NoMask => "female_none", + FemaleClothingMask.UniformTop => "female_top", + _ => "female_full", + }); + } + return; } } diff --git a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs index ef6ac85f17..44b4b7e4ce 100644 --- a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs @@ -47,6 +47,8 @@ namespace Content.Client.GameObjects.Components.Mobs sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "human_chest_m" : "human_chest_f"); sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "human_head_m" : "human_head_f"); + sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female); + var hairStyle = Appearance.HairStyleName; if (string.IsNullOrWhiteSpace(hairStyle) || !HairStyles.HairStylesMap.ContainsKey(hairStyle)) hairStyle = HairStyles.DefaultHairStyle; diff --git a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs index 253ca32729..265803fb5e 100644 --- a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs @@ -12,5 +12,6 @@ namespace Content.Shared.Preferences.Appearance LHand, RLeg, LLeg, + StencilMask } } diff --git a/Resources/Prototypes/Entities/items/clothing/uniforms.yml b/Resources/Prototypes/Entities/items/clothing/uniforms.yml index fa65553d34..747ff57d28 100644 --- a/Resources/Prototypes/Entities/items/clothing/uniforms.yml +++ b/Resources/Prototypes/Entities/items/clothing/uniforms.yml @@ -91,6 +91,7 @@ - type: Clothing sprite: Clothing/uniform_clown.rsi + femaleMask: UniformTop - type: entity parent: UniformBase @@ -141,4 +142,4 @@ state: captain - type: Clothing - sprite: Clothing/captain_uniform.rsi + sprite: Clothing/captain_uniform.rsi diff --git a/Resources/Prototypes/Entities/items/toolbox.yml b/Resources/Prototypes/Entities/items/toolbox.yml index b24918463b..b0c992b289 100644 --- a/Resources/Prototypes/Entities/items/toolbox.yml +++ b/Resources/Prototypes/Entities/items/toolbox.yml @@ -17,7 +17,14 @@ description: A bright red toolbox, stocked with emergency tools components: - type: Sprite - texture: Objects/Tools/toolbox_r.png + layers: + - shader: stencilClear + - texture: Objects/Tools/wrench.png + shader: stencilMask + + - texture: Objects/Tools/toolbox_r.png + shader: stencilDraw + - type: Icon texture: Objects/Tools/toolbox_r.png diff --git a/Resources/Prototypes/Entities/mobs/human.yml b/Resources/Prototypes/Entities/mobs/human.yml index 7e818e5534..88ad32a9c2 100644 --- a/Resources/Prototypes/Entities/mobs/human.yml +++ b/Resources/Prototypes/Entities/mobs/human.yml @@ -58,7 +58,18 @@ sprite: Mob/human.rsi state: human_l_leg + - shader: stencilClear + sprite: Mob/human.rsi + state: human_l_leg + - shader: stencilMask + map: ["enum.HumanoidVisualLayers.StencilMask"] + sprite: Mob/masking_helpers.rsi + state: female_full + visible: false + - map: ["enum.Slots.INNERCLOTHING"] + shader: stencilDraw + - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -174,7 +185,16 @@ sprite: Mob/human.rsi state: human_l_leg + - shader: stencilClear + - shader: stencilMask + map: ["enum.HumanoidVisualLayers.StencilMask"] + sprite: Mob/masking_helpers.rsi + state: female_full + visible: false + - map: ["enum.Slots.INNERCLOTHING"] + shader: stencilDraw + - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] diff --git a/Resources/Prototypes/Shaders/Stencils.yml b/Resources/Prototypes/Shaders/Stencils.yml new file mode 100644 index 0000000000..bf4efc200d --- /dev/null +++ b/Resources/Prototypes/Shaders/Stencils.yml @@ -0,0 +1,25 @@ +- type: shader + id: stencilClear + kind: source + path: "/Shaders/stencilclear.swsl" + stencil: + ref: 0 + op: Replace + func: Always + +- type: shader + id: stencilMask + kind: source + path: "/Shaders/stencilmask.swsl" + stencil: + ref: 1 + op: Replace + func: Always + +- type: shader + id: stencilDraw + kind: canvas + stencil: + ref: 1 + op: Keep + func: NotEqual diff --git a/Resources/Shaders/stencilclear.swsl b/Resources/Shaders/stencilclear.swsl new file mode 100644 index 0000000000..b0ec17e0f9 --- /dev/null +++ b/Resources/Shaders/stencilclear.swsl @@ -0,0 +1,3 @@ +void fragment() { + COLOR = vec4(0); +} diff --git a/Resources/Shaders/stencilmask.swsl b/Resources/Shaders/stencilmask.swsl new file mode 100644 index 0000000000..0e50894995 --- /dev/null +++ b/Resources/Shaders/stencilmask.swsl @@ -0,0 +1,7 @@ +void fragment() { + if (texture(TEXTURE, UV).a == 0) { + discard; // Discard if no alpha so that there's a hole in the stencil buffer. + } + + COLOR = vec4(0); +} diff --git a/Resources/Textures/Mob/masking_helpers.rsi/female_full.png b/Resources/Textures/Mob/masking_helpers.rsi/female_full.png new file mode 100644 index 0000000000..cd5127bc90 Binary files /dev/null and b/Resources/Textures/Mob/masking_helpers.rsi/female_full.png differ diff --git a/Resources/Textures/Mob/masking_helpers.rsi/female_none.png b/Resources/Textures/Mob/masking_helpers.rsi/female_none.png new file mode 100644 index 0000000000..6e3cb09bcf Binary files /dev/null and b/Resources/Textures/Mob/masking_helpers.rsi/female_none.png differ diff --git a/Resources/Textures/Mob/masking_helpers.rsi/female_top.png b/Resources/Textures/Mob/masking_helpers.rsi/female_top.png new file mode 100644 index 0000000000..126c9cb829 Binary files /dev/null and b/Resources/Textures/Mob/masking_helpers.rsi/female_top.png differ diff --git a/Resources/Textures/Mob/masking_helpers.rsi/meta.json b/Resources/Textures/Mob/masking_helpers.rsi/meta.json new file mode 100644 index 0000000000..530b0c4848 --- /dev/null +++ b/Resources/Textures/Mob/masking_helpers.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "female_full", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "female_top", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file