Repository: Fansana/floofstation1
Pull request #801: You Can Wear Two Sets of Undies, and You Can Take Off Individual Undies Too
Author: login: Superlagg
Labels: name: Changes: C#
description: Changes any cs files
name: Changes: Localization
description: Changes any ftl files
name: Changes: YML
description: Changes any yml files
name: Status: Needs Review
description: Someone please review this.
name: Priority 1: Urgent
description: Higher priority than other stuff, but not an emergency.
Created: 2025-04-18T23:16:15Z | Merged: 2025-04-27T15:13:03Z

# Description

Critters that can wear underwear can put on 2 undershirts and 2 underpants, so you can wear a bra under a shirt, and panties under some other kind of panties.

Also adds in a system that lets you pick which undie marking to show or hide, so you can take your shirt off and still have your bra on!

Why? The old system worked based on bodypart slots, which meant that it would make *everything* in the respective undies slots hidden or visible.

May cause merge conflicts with my other PR that touches HumanoidAppearanceSystem, but I can resolve those, no worries!

---

# Changelog

<!--
You can add an author after the `:cl:` to change the name that appears in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl:
- add: You can now take off / put on individual undies. This'll be great, because...
- tweak: You can wear two undershirts and two underpants! You can wear a bra under a shirt, and panties under some other kind of panties!



Diff:
diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
index dcd3e9e00c9..ab2386529d2 100644
--- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
+++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
@@ -325,6 +325,7 @@ SpriteComponent sprite
         // lets just kinda sorta do that ourselves
         var layerDict = new Dictionary<string, int>();
 
+        visible &= !humanoid.HiddenMarkings.Contains(markingPrototype.ID); // FLOOF ADD
         // FLOOF ADD END
 
         for (var j = 0; j < markingPrototype.Sprites.Count; j++)
diff --git a/Content.Server/_Floof/ModifyUndies/ModifyUndiesComponent.cs b/Content.Server/_Floof/ModifyUndies/ModifyUndiesComponent.cs
new file mode 100644
index 00000000000..75d85f16d75
--- /dev/null
+++ b/Content.Server/_Floof/ModifyUndies/ModifyUndiesComponent.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Humanoid;
+
+
+namespace Content.Server.FloofStation.ModifyUndies;
+
+
+/// <summary>
+/// This is used for...
+/// </summary>
+[RegisterComponent]
+public sealed partial class ModifyUndiesComponent : Component
+{
+    /// <summary>
+    ///     The bodypart target enums for the undies.
+    /// </summary>
+    public List<HumanoidVisualLayers> BodyPartTargets =
+    [
+        HumanoidVisualLayers.Underwear,
+        HumanoidVisualLayers.Undershirt
+    ];
+}
+
+
diff --git a/Content.Server/_Floof/ModifyUndies/ModifyUndiesSystem.cs b/Content.Server/_Floof/ModifyUndies/ModifyUndiesSystem.cs
new file mode 100644
index 00000000000..c822d743f55
--- /dev/null
+++ b/Content.Server/_Floof/ModifyUndies/ModifyUndiesSystem.cs
@@ -0,0 +1,249 @@
+using System.Linq;
+using Content.Server.Humanoid;
+using Content.Shared.DoAfter;
+using Content.Shared.FloofStation;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Markings;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Inventory;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Robust.Server.Audio;
+using Robust.Shared.Audio;
+using Robust.Shared.Player;
+using Robust.Shared.Utility;
+
+
+namespace Content.Server.FloofStation.ModifyUndies;
+
+
+/// <summary>
+/// This is a component that lets you show/hide specific underwear slots.
+///
+/// </summary>
+public sealed class ModifyUndiesSystem : EntitySystem
+{
+    [Dependency] private readonly MarkingManager _markingManager = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly AudioSystem _audio = default!;
+    [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+    [Dependency] private readonly EntityManager _entMan = default!;
+
+    public static readonly VerbCategory UndiesCat =
+        new("verb-categories-undies", "/Textures/Interface/VerbIcons/undies.png");
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+        SubscribeLocalEvent<ModifyUndiesComponent, GetVerbsEvent<Verb>>(AddModifyUndiesVerb);
+        SubscribeLocalEvent<ModifyUndiesComponent, ModifyUndiesDoAfterEvent>(ToggleUndies);
+    }
+
+    private void AddModifyUndiesVerb(EntityUid uid, ModifyUndiesComponent component, GetVerbsEvent<Verb> args)
+    {
+        if (args.Hands == null || !args.CanAccess ||!args.CanInteract)
+            return;
+        if (!TryComp<HumanoidAppearanceComponent>(args.Target, out var humApp))
+            return;
+        if (args.User != args.Target && _entMan.System<InventorySystem>().TryGetSlotEntity(args.Target, "jumpsuit", out _))
+            return; // mainly so people cant just spy on others undies *too* easily
+        var isMine = args.User == args.Target;
+        // okay go through their markings, and find all the undershirts and underwear markings
+        // <marking_ID>, list:(localized name, bodypart enum, isvisible)
+        foreach (var marking in humApp.MarkingSet.Markings.Values.SelectMany(markingLust => markingLust))
+        {
+            if (!_markingManager.TryGetMarking(marking, out var mProt))
+                continue;
+            // check if the Bodypart is in the component's BodyPartTargets
+            if (!component.BodyPartTargets.Contains(mProt.BodyPart))
+                continue;
+            var localizedName = Loc.GetString($"marking-{mProt.ID}");
+            var partSlot = mProt.BodyPart;
+            var isVisible = !humApp.HiddenMarkings.Contains(mProt.ID);
+            if (mProt.Sprites.Count < 1)
+                continue; // no sprites means its not visible means its kinda already off and you cant put it on
+            var undieOrBra = partSlot switch
+            {
+                HumanoidVisualLayers.Undershirt => new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/bra.png")),
+                HumanoidVisualLayers.Underwear => new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/underpants.png")),
+                _ => new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/undies.png"))
+            };
+            // add the verb
+            Verb verb = new()
+            {
+                Text = Loc.GetString(
+                    "modify-undies-verb-text",
+                    ("undies", localizedName),
+                    ("isVisible", isVisible),
+                    ("isMine", isMine),
+                    ("target", Identity.Entity(args.Target, EntityManager))
+                    ),
+                Icon = undieOrBra,
+                Category = UndiesCat,
+                Act = () =>
+                {
+                    var ev = new ModifyUndiesDoAfterEvent(
+                        marking,
+                        localizedName,
+                        isVisible
+                        );
+                    var doAfterArgs = new DoAfterArgs(
+                        EntityManager,
+                        args.User,
+                        2f,
+                        ev,
+                        args.Target,
+                        args.Target,
+                        used: args.User
+                    )
+                    {
+                        Hidden = false,
+                        MovementThreshold = 0,
+                        RequireCanInteract = true,
+                        BlockDuplicate = true
+                    };
+                    string gString;
+                    if (args.User == args.Target)
+                    {
+                        gString = isVisible
+                            ? "undies-removed-self-start"
+                            : "undies-equipped-self-start";
+                        _popupSystem.PopupCoordinates(
+                            Loc.GetString(
+                                gString,
+                                ("undie", localizedName)
+                                ),
+                            Transform(args.Target).Coordinates,
+                            Filter.Entities(args.Target),
+                            true,
+                            PopupType.Medium);
+                    }
+                    // someone doing this to someone else
+                    else
+                    {
+                        // to the user
+                        gString = isVisible
+                            ? "undies-removed-user-start"
+                            : "undies-equipped-user-start";
+                        _popupSystem.PopupCoordinates(
+                            Loc.GetString(
+                                gString,
+                                ("undie", localizedName)
+                                ),
+                            Transform(args.Target).Coordinates,
+                            Filter.Entities(args.User),
+                            true,
+                            PopupType.Medium);
+                        // to the target
+                        gString = isVisible
+                            ? "undies-removed-target-start"
+                            : "undies-equipped-target-start";
+                        _popupSystem.PopupCoordinates(
+                            Loc.GetString(
+                                gString,
+                                ("undie", localizedName),
+                                ("user", Identity.Entity(args.User, EntityManager))
+                                ),
+                            Transform(args.Target).Coordinates,
+                            Filter.Entities(args.Target),
+                            true,
+                            PopupType.MediumCaution);
+                    }
+                    // and then play a sound!
+                    var rufthleAudio = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
+                    _audio.PlayEntity(
+                        rufthleAudio,
+                        Filter.Entities(args.User, args.Target),
+                        args.Target,
+                        false,
+                        AudioParams.Default.WithVariation(0.5f).WithVolume(0.5f));
+                    _doAfterSystem.TryStartDoAfter(doAfterArgs);
+                    // ToggleUndies(uid, mProt, isVisible, localizedName, args.User, args.Target, humApp);
+                },
+                Disabled = false,
+                Message = null
+            };
+            args.Verbs.Add(verb);
+        }
+    }
+    private void ToggleUndies(
+        EntityUid uid,
+        ModifyUndiesComponent component,
+        ModifyUndiesDoAfterEvent args
+        )
+    {
+        if (!_markingManager.TryGetMarking(args.Marking, out var mProt))
+            return;
+        if (!TryComp<HumanoidAppearanceComponent>(args.Target, out var humApp))
+            return;
+
+        _humanoid.SetMarkingVisibility(
+            uid,
+            humApp,
+            mProt.ID,
+            !args.IsVisible
+        );
+        // then make a text bubble!
+        // one for the doer, one for the target
+        // and one if the doer is the target
+        // Effect targets for different players
+        // Popups
+        string gString;
+        if (args.User == args.Target.Value)
+        {
+            gString = args.IsVisible
+                ? "undies-removed-self"
+                : "undies-equipped-self";
+            _popupSystem.PopupCoordinates(
+                Loc.GetString(
+                    gString,
+                    ("undie", args.MarkingPrototypeName)
+                    ),
+                Transform(args.Target.Value).Coordinates,
+                Filter.Entities(args.Target.Value),
+                true,
+                PopupType.Medium);
+        }
+        // someone doing this to someone else
+        else
+        {
+            // to the user
+            gString = args.IsVisible
+                ? "undies-removed-user"
+                : "undies-equipped-user";
+            _popupSystem.PopupCoordinates(
+                Loc.GetString(
+                    gString,
+                    ("undie", args.MarkingPrototypeName)
+                    ),
+                Transform(args.Target.Value).Coordinates,
+                Filter.Entities(args.User),
+                true,
+                PopupType.Medium);
+            // to the target
+            gString = args.IsVisible
+                ? "undies-removed-target"
+                : "undies-equipped-target";
+            _popupSystem.PopupCoordinates(
+                Loc.GetString(
+                    gString,
+                    ("undie", args.MarkingPrototypeName),
+                    ("user", Identity.Entity(args.User, EntityManager))
+                    ),
+                Transform(args.Target.Value).Coordinates,
+                Filter.Entities(args.Target.Value),
+                true,
+                PopupType.Medium);
+        }
+        // and then play a sound!
+        var rufthleAudio = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg");
+        _audio.PlayEntity(
+            rufthleAudio,
+            Filter.Entities(args.User, args.Target.Value),
+            args.Target.Value,
+            false,
+            AudioParams.Default.WithVariation(0.5f).WithVolume(0.5f));
+    }
+}
diff --git a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
index c0f9b8ac00f..106bae2395a 100644
--- a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
+++ b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs
@@ -70,6 +70,15 @@ public sealed partial class HumanoidAppearanceComponent : Component
     [DataField, AutoNetworkedField]
     public HashSet<HumanoidVisualLayers> HiddenLayers = new();
 
+    /// <summary>
+    /// The specific markings that are hidden, whether or not the layer is hidden.
+    /// This is so we can just turn off a single marking, or part of a single marking.
+    /// (cus underwear, its for underwear, so you can take off your bra and still have your shirt on)
+    /// FLOOF ADD
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<string> HiddenMarkings = new();
+
     [DataField, AutoNetworkedField]
     public Sex Sex = Sex.Male;
 
diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
index 8c74cfefa5d..bceb4b637da 100644
--- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
+++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
@@ -362,7 +362,7 @@ public void SetScale(EntityUid uid, Vector2 scale, bool sync = true, HumanoidApp
         }
 
         if (sync)
-            Dirty(uid, humanoid);    
+            Dirty(uid, humanoid);
     }
 
     /// <summary>
@@ -545,4 +545,25 @@ public string GetAgeRepresentation(string species, int age)
 
         return Loc.GetString("identity-age-old");
     }
+
+    // Floofstation section
+    public void SetMarkingVisibility(
+        EntityUid uid,
+        HumanoidAppearanceComponent? humanoid,
+        string markingId,
+        bool visible)
+    {
+        if (!_markingManager.Markings.TryGetValue(markingId, out var prototype))
+            return;
+        if (!Resolve(uid, ref humanoid))
+            return;
+
+        if (visible)
+            humanoid.HiddenMarkings.Remove(markingId);
+        else
+            humanoid.HiddenMarkings.Add(markingId);
+
+        Dirty(uid, humanoid);
+    }
+    // Floofstation section end
 }
diff --git a/Content.Shared/_Floof/ModifyUndiesDoAfterEvent.cs b/Content.Shared/_Floof/ModifyUndiesDoAfterEvent.cs
new file mode 100644
index 00000000000..33d705c7299
--- /dev/null
+++ b/Content.Shared/_Floof/ModifyUndiesDoAfterEvent.cs
@@ -0,0 +1,51 @@
+using Content.Shared.DoAfter;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Markings;
+using Robust.Shared.Serialization;
+
+
+namespace Content.Shared.FloofStation;
+
+
+[Serializable, NetSerializable]
+public sealed partial class ModifyUndiesDoAfterEvent : DoAfterEvent
+{
+    /// <summary>
+    ///     The marking prototype that is being modified.
+    /// </summary>
+    [DataField("markingPrototype", required: true)]
+    public Marking Marking;
+
+    /// <summary>
+    ///     Localized string for the marking prototype.
+    /// </summary>
+    [DataField("markingPrototypeName", required: true)]
+    public string MarkingPrototypeName;
+
+    /// <summary>
+    ///     Whether or not the marking is visible at the moment.
+    /// </summary>
+    [DataField("visible", required: true)]
+    public bool IsVisible;
+
+    private ModifyUndiesDoAfterEvent()
+    {
+        Marking = default!;
+        MarkingPrototypeName = string.Empty;
+        IsVisible = false;
+    }
+
+    public ModifyUndiesDoAfterEvent(
+        Marking marking,
+        string markingPrototypeName,
+        bool isVisible
+        )
+    {
+        Marking = marking;
+        MarkingPrototypeName = markingPrototypeName;
+        IsVisible = isVisible;
+    }
+
+    public override DoAfterEvent Clone() => this;
+}
+
diff --git a/Resources/Locale/en-US/_Floof/markings/modify_undies.ftl b/Resources/Locale/en-US/_Floof/markings/modify_undies.ftl
new file mode 100644
index 00000000000..62877c31f0c
--- /dev/null
+++ b/Resources/Locale/en-US/_Floof/markings/modify_undies.ftl
@@ -0,0 +1,27 @@
+verb-categories-undies = Modify Undies
+
+modify-undies-verb-text = {$isVisible ->
+*[false] Put on
+[true] Take off
+} {$isMine ->
+*[false] {$target}'s
+[true] your
+} {$undies}
+
+undies-removed-self-start = You start taking off your {$undie}.
+undies-equipped-self-start = You start putting on your {$undie}.
+
+undies-removed-user-start = You start taking their {$undie} off.
+undies-equipped-user-start = You start putting their {$undie} on them.
+
+undies-removed-target-start = {PROPER($user)} starts taking your {$undie} off.
+undies-equipped-target-start = {PROPER($user)} starts putting your {$undie} on you.
+
+undies-removed-self = You take off your {$undie}.
+undies-equipped-self = You put on your {$undie}.
+
+undies-removed-user = You take their {$undie} off.
+undies-equipped-user = You put their {$undie} on them.
+
+undies-removed-target = {PROPER($user)} takes your {$undie} off.
+undies-equipped-target = {PROPER($user)} puts your {$undie} on you.
diff --git a/Resources/Prototypes/DeltaV/Species/chitinid.yml b/Resources/Prototypes/DeltaV/Species/chitinid.yml
index b2ce4ede3c8..3b5042294c0 100644
--- a/Resources/Prototypes/DeltaV/Species/chitinid.yml
+++ b/Resources/Prototypes/DeltaV/Species/chitinid.yml
@@ -47,11 +47,11 @@
   id: MobChitinidMarkingLimits
   onlyWhitelisted: true
   points:
-    Underwear: # Floofstation
-      points: 1
+    Underwear: # Floofstation # FLOOF CHANGE
+      points: 2
       required: false
-    Undershirt: # Floofstation
-      points: 1
+    Undershirt: # Floofstation # FLOOF CHANGE
+      points: 2
       required: false
     Hair:
       points: 0
diff --git a/Resources/Prototypes/DeltaV/Species/feroxi.yml b/Resources/Prototypes/DeltaV/Species/feroxi.yml
index f2745ee336c..96f35a915e9 100644
--- a/Resources/Prototypes/DeltaV/Species/feroxi.yml
+++ b/Resources/Prototypes/DeltaV/Species/feroxi.yml
@@ -42,10 +42,10 @@
   id: MobFeroxiMarkingLimits
   points:
     Underwear: # Floofstation
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false
     Undershirt: # Floofstation
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false
     Hair:
       points: 1
diff --git a/Resources/Prototypes/DeltaV/Species/rodentia.yml b/Resources/Prototypes/DeltaV/Species/rodentia.yml
index 7c6c3d4a0eb..dbeae9de7c5 100644
--- a/Resources/Prototypes/DeltaV/Species/rodentia.yml
+++ b/Resources/Prototypes/DeltaV/Species/rodentia.yml
@@ -50,10 +50,10 @@
   id: MobRodentiaMarkingLimits
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1 # Floof, add underwear
+      points: 2 # Floof, add underwear # FLOOF CHANGE
       required: false
     Hair:
       points: 1
diff --git a/Resources/Prototypes/DeltaV/Species/vulpkanin.yml b/Resources/Prototypes/DeltaV/Species/vulpkanin.yml
index 868f495de19..2ce19fe1dc9 100644
--- a/Resources/Prototypes/DeltaV/Species/vulpkanin.yml
+++ b/Resources/Prototypes/DeltaV/Species/vulpkanin.yml
@@ -47,10 +47,10 @@
       points: 1
       required: false
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1 # Floof, add underwear
+      points: 2 # Floof, add underwear # FLOOF CHANGE
       required: false
     Hair:
       points: 1
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index 02627f71079..0f6cfba6308 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -241,6 +241,7 @@
   - type: SurgeryTarget
   - type: FootPrints
   - type: Wagging
+  - type: ModifyUndies  # FLOOF Add
 
 - type: entity
   save: false
diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml
index fcdc154a0bd..55cc3e96eb3 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/human.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml
@@ -21,6 +21,7 @@
     hideLayersOnEquip:
     - Hair
     - Snout
+  - type: ModifyUndies # FLOOF CHANGE
   - type: LanguageKnowledge
     speaks:
     - TauCetiBasic
diff --git a/Resources/Prototypes/Species/arachne.yml b/Resources/Prototypes/Species/arachne.yml
index 035bd87419a..ad1d5a72f31 100644
--- a/Resources/Prototypes/Species/arachne.yml
+++ b/Resources/Prototypes/Species/arachne.yml
@@ -17,7 +17,7 @@
   id: MobArachneMarkingLimits
   points:
     Undershirt:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Face:
       points: 1
diff --git a/Resources/Prototypes/Species/arachnid.yml b/Resources/Prototypes/Species/arachnid.yml
index 951b0b31dd0..518829d8d6b 100644
--- a/Resources/Prototypes/Species/arachnid.yml
+++ b/Resources/Prototypes/Species/arachnid.yml
@@ -50,10 +50,10 @@
   onlyWhitelisted: true
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Hair:
       points: 0
diff --git a/Resources/Prototypes/Species/diona.yml b/Resources/Prototypes/Species/diona.yml
index e214a79fe38..11e2ae370f1 100644
--- a/Resources/Prototypes/Species/diona.yml
+++ b/Resources/Prototypes/Species/diona.yml
@@ -42,10 +42,10 @@
   onlyWhitelisted: true
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Head:
       points: 2
diff --git a/Resources/Prototypes/Species/harpy.yml b/Resources/Prototypes/Species/harpy.yml
index c91d38bf8b9..c93699ab94e 100644
--- a/Resources/Prototypes/Species/harpy.yml
+++ b/Resources/Prototypes/Species/harpy.yml
@@ -47,10 +47,10 @@
   id: MobHarpyMarkingLimits
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1 # Floof, add underwear
+      points: 2 # Floof, add underwear # FLOOF CHANGE
       required: false
     Face:
       points: 1
diff --git a/Resources/Prototypes/Species/human.yml b/Resources/Prototypes/Species/human.yml
index 45781b405d2..44b4c4539ff 100644
--- a/Resources/Prototypes/Species/human.yml
+++ b/Resources/Prototypes/Species/human.yml
@@ -46,10 +46,10 @@
   id: MobHumanMarkingLimits
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Face:
       points: 1
diff --git a/Resources/Prototypes/Species/ipc.yml b/Resources/Prototypes/Species/ipc.yml
index e2eead327a8..bd774bcac5a 100644
--- a/Resources/Prototypes/Species/ipc.yml
+++ b/Resources/Prototypes/Species/ipc.yml
@@ -53,10 +53,10 @@
   id: MobIPCMarkingLimits
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
-    Undershirt:
-      points: 1 # Floof, add underwear
+    Undershirt: # FLOOF CHANGE
+      points: 2 # Floof, add underwear
       required: false
     Head:
       points: 1
diff --git a/Resources/Prototypes/Species/moth.yml b/Resources/Prototypes/Species/moth.yml
index 7cf4a69acde..ed89b238b8c 100644
--- a/Resources/Prototypes/Species/moth.yml
+++ b/Resources/Prototypes/Species/moth.yml
@@ -47,10 +47,10 @@
   onlyWhitelisted: true
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1 # Floof, add underwear
+      points: 2 # Floof, add underwear # FLOOF CHANGE
       required: false
     Hair:
       points: 0
diff --git a/Resources/Prototypes/Species/reptilian.yml b/Resources/Prototypes/Species/reptilian.yml
index f8980415518..a9944aea199 100644
--- a/Resources/Prototypes/Species/reptilian.yml
+++ b/Resources/Prototypes/Species/reptilian.yml
@@ -54,10 +54,10 @@
       points: 1
       required: false
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1 # Floof, add underwear
+      points: 2 # Floof, add underwear # FLOOF CHANGE
       required: false
     Hair:
       points: 0
diff --git a/Resources/Prototypes/Species/shadowkin.yml b/Resources/Prototypes/Species/shadowkin.yml
index 0de3fe6884b..b080f066bc4 100644
--- a/Resources/Prototypes/Species/shadowkin.yml
+++ b/Resources/Prototypes/Species/shadowkin.yml
@@ -53,10 +53,10 @@
   id: MobShadowkinMarkingLimits
   points:
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Tail:
       points: 1
diff --git a/Resources/Prototypes/Species/slime.yml b/Resources/Prototypes/Species/slime.yml
index 9c7be269a6a..311fd76c60d 100644
--- a/Resources/Prototypes/Species/slime.yml
+++ b/Resources/Prototypes/Species/slime.yml
@@ -44,10 +44,10 @@
       points: 1
       required: false
     Underwear:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Undershirt:
-      points: 1
+      points: 2 # FLOOF CHANGE
       required: false # Floof, add underwear
     Hair:
       points: 1
diff --git a/Resources/Prototypes/_Floof/Interactions/underwear_interactions.yml b/Resources/Prototypes/_Floof/Interactions/underwear_interactions.yml
index c990aa5996e..04499a803cf 100644
--- a/Resources/Prototypes/_Floof/Interactions/underwear_interactions.yml
+++ b/Resources/Prototypes/_Floof/Interactions/underwear_interactions.yml
@@ -1,39 +1,39 @@
-- type: Interaction
-  parent: [BaseGlobal, BaseHands]
-  id: ToggleUndershirt
-  priority: -11
-  delay: 3
-  allowSelfInteract: true
-  hideByRequirement: true
-  requirement:
-    !type:ComplexRequirement
-    requirements:
-    - !type:EntityWhitelistRequirement
-      whitelist:
-        components: [HumanoidAppearance]
-    - !type:ClothingSlotBlacklistRequirement
-      slot: jumpsuit
-  effectSuccess:
-    popup: Obvious
-    sound: {path: /Audio/Effects/thudswoosh.ogg}
-    soundPerceivedByOthers: false
-  action:
-    !type:ToggleLayersAction
-    neededMarkingCategory: Undershirt
-    toggleLayers:
-    - Undershirt
-
-- type: Interaction
-  parent: ToggleUndershirt
-  id: ToggleUnderwear
-  priority: -12
-  delay: 2.2
-  effectSuccess:
-    popup: Obvious
-    sound: {path: /Audio/Effects/thudswoosh.ogg}
-    soundPerceivedByOthers: false
-  action:
-    !type:ToggleLayersAction
-    neededMarkingCategory: Underwear
-    toggleLayers:
-    - Underwear
+# - type: Interaction
+#   parent: [BaseGlobal, BaseHands]
+#   id: ToggleUndershirt
+#   priority: -11
+#   delay: 3
+#   allowSelfInteract: true
+#   hideByRequirement: true
+#   requirement:
+#     !type:ComplexRequirement
+#     requirements:
+#     - !type:EntityWhitelistRequirement
+#       whitelist:
+#         components: [HumanoidAppearance]
+#     - !type:ClothingSlotBlacklistRequirement
+#       slot: jumpsuit
+#   effectSuccess:
+#     popup: Obvious
+#     sound: {path: /Audio/Effects/thudswoosh.ogg}
+#     soundPerceivedByOthers: false
+#   action:
+#     !type:ToggleLayersAction
+#     neededMarkingCategory: Undershirt
+#     toggleLayers:
+#     - Undershirt
+#
+# - type: Interaction
+#   parent: ToggleUndershirt
+#   id: ToggleUnderwear
+#   priority: -12
+#   delay: 2.2
+#   effectSuccess:
+#     popup: Obvious
+#     sound: {path: /Audio/Effects/thudswoosh.ogg}
+#     soundPerceivedByOthers: false
+#   action:
+#     !type:ToggleLayersAction
+#     neededMarkingCategory: Underwear
+#     toggleLayers:
+#     - Underwear
diff --git a/Resources/Textures/Interface/VerbIcons/bra.png b/Resources/Textures/Interface/VerbIcons/bra.png
new file mode 100644
index 00000000000..e38b1e166e7
Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/bra.png differ
diff --git a/Resources/Textures/Interface/VerbIcons/underpants.png b/Resources/Textures/Interface/VerbIcons/underpants.png
new file mode 100644
index 00000000000..1c6c9c0e522
Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/underpants.png differ
diff --git a/Resources/Textures/Interface/VerbIcons/undies.png b/Resources/Textures/Interface/VerbIcons/undies.png
new file mode 100644
index 00000000000..c3b1f8e8f19
Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/undies.png differ


Reviews:
user: login: Memeji
body: Seems a good thing to try!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-19T00:49:26Z
user: login: Mnemotechnician
body: Don't have any significant objections for the code, but we already have two interaction verbs that let you show/hide underwear when the jumpsuit slot is empty (added by the author of the original underwear PR). You should either comment those out as obsolete, or integrate your new system with those.

Additionally, changes to upstream systems and other files need to be commented as belonging to floofstation (see how other PRs do it)
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-19T01:20:51Z
user: login: Mnemotechnician
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-19T01:21:41Z
user: login: Superlagg
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T01:27:37Z
user: login: Mnemotechnician
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-21T12:57:51Z
user: login: Memeji
body: Merge conflict from your other PR as you described, just need that resolved and should be good.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T13:46:40Z
user: login: ShadesMars
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T20:16:10Z

Review comments:
diff_hunk: @@ -21,6 +21,7 @@
     <ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj" />
     <ProjectReference Include="..\Content.Shared.Database\Content.Shared.Database.csproj" />
     <ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
+    <ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
path: Content.Server/Content.Server.csproj
user: login: Mnemotechnician
body: Why is this here though?
created_at: 2025-04-19T01:21:41Z
author_association: COLLABORATOR
diff_hunk: @@ -21,6 +21,7 @@
     <ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj" />
     <ProjectReference Include="..\Content.Shared.Database\Content.Shared.Database.csproj" />
     <ProjectReference Include="..\RobustToolbox\Lidgren.Network\Lidgren.Network.csproj" />
+    <ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
path: Content.Server/Content.Server.csproj
user: login: Superlagg
body: i dont know, imma take that out. dunno why it keeps trying to commit stuff like this
created_at: 2025-04-19T01:27:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: Superlagg
created_at: 2025-04-19T01:37:51Z
body: > Don't have any significant objections for the code, but we already have two interaction verbs that let you show/hide underwear when the jumpsuit slot is empty (added by the author of the original underwear PR). You should either comment those out as obsolete, or integrate your new system with those.
> 
> Additionally, changes to upstream systems and other files need to be commented as belonging to floofstation (see how other PRs do it)

I commented out the interactions, so it's just my system. I also added the comments, and undid the nonfunctional extra hair point. Turns out, even though the system seems like it should support multiple hairs, I couldnt get it to work!
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-21T14:57:51Z
body: This pull request has conflicts, please resolve those before we can evaluate the pull request.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-24T03:28:32Z
body: This pull request has conflicts, please resolve those before we can evaluate the pull request.
author_association: CONTRIBUTOR
user: login: Superlagg
created_at: 2025-04-24T22:49:54Z
body: should be merge conflict free!
author_association: CONTRIBUTOR