From ec041a0f228d14605e3869cf4f69a47ad29cf157 Mon Sep 17 00:00:00 2001 From: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Date: Sun, 25 Jan 2026 10:31:55 -0600 Subject: [PATCH] Of Hats and Hardsuit Helmets (#5263) * Now you can toggle your helm with a hat on * Added container to toggleable clothing in YAML. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed YAML fail. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../EntitySystems/ToggleableClothingSystem.cs | 40 ++++++-- ...ggleableClothingComponent.UnderClothing.cs | 27 ++++++ .../ToggleableClothingSystem.UnderClothing.cs | 94 +++++++++++++++++++ .../Entities/Clothing/Neck/cloaks.yml | 3 + .../OuterClothing/base_clothingouter.yml | 3 + .../Clothing/OuterClothing/hardsuits.yml | 1 + .../Clothing/OuterClothing/softsuits.yml | 2 + .../Entities/Clothing/OuterClothing/suits.yml | 4 + .../Clothing/OuterClothing/wintercoats.yml | 1 + .../Clothing/OuterClothing/hardsuits.yml | 7 +- .../Clothing/OuterClothing/wintercoats.yml | 9 +- .../Clothing/OuterClothing/softsuits.yml | 2 + .../Clothing/OuterClothing/wintercoats.yml | 1 + 13 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 Content.Shared/_DV/Clothing/Components/ToggleableClothingComponent.UnderClothing.cs create mode 100644 Content.Shared/_DV/Clothing/EntitySystems/ToggleableClothingSystem.UnderClothing.cs diff --git a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs index a2b7d01641..d3c9a97db7 100644 --- a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs @@ -16,7 +16,7 @@ using Robust.Shared.Utility; namespace Content.Shared.Clothing.EntitySystems; -public sealed class ToggleableClothingSystem : EntitySystem +public sealed partial class ToggleableClothingSystem : EntitySystem // DeltaV - Made Partial { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _netMan = default!; @@ -140,10 +140,12 @@ public sealed class ToggleableClothingSystem : EntitySystem || toggleCom.Container == null) return; + var parent = Transform(uid).ParentUid; // DeltaV - Allow hats under toggleable helms if (!_inventorySystem.TryUnequip(Transform(uid).ParentUid, toggleCom.Slot, force: true)) return; _containerSystem.Insert(uid, toggleCom.Container); + TryEquipUnderClothing(parent, component); // DeltaV - Allow hats under toggleable helms args.Handled = true; } @@ -156,11 +158,17 @@ public sealed class ToggleableClothingSystem : EntitySystem if (_timing.ApplyingState) return; + var wasAttachedUnequipped = false; // DeltaV - Allow hats under toggleable helms + // If the attached clothing is not currently in the container, this just assumes that it is currently equipped. // This should maybe double check that the entity currently in the slot is actually the attached clothing, but // if its not, then something else has gone wrong already... if (component.Container != null && component.Container.ContainedEntity == null && component.ClothingUid != null) - _inventorySystem.TryUnequip(args.Equipee, component.Slot, force: true, triggerHandContact: true); + wasAttachedUnequipped = _inventorySystem.TryUnequip(args.Equipee, component.Slot, force: true, triggerHandContact: true); // DeltaV - Allow hats under toggleable helms + + // DeltaV - If the toggleable helm was uneqipped, try to equip whats in the under clothing container + if (wasAttachedUnequipped && !TryEquipUnderClothing(args.Equipee, component)) + TryDropUnderClothing(component); } private void OnRemoveToggleable(EntityUid uid, ToggleableClothingComponent component, ComponentRemove args) @@ -240,15 +248,30 @@ public sealed class ToggleableClothingSystem : EntitySystem return; var parent = Transform(target).ParentUid; + + // Begin DeltaV - Allow hats under toggleable helms! + var wasAttachedUnequipped = false; // We want to track if the toggleable item was unequipped, assume false for now. + if (component.Container.ContainedEntity == null) - _inventorySystem.TryUnequip(user, parent, component.Slot, force: true); - else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing)) - { - _popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)), - user, user); - } + wasAttachedUnequipped = _inventorySystem.TryUnequip(user, parent, component.Slot, force: true); + else + { + if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing) + && !TryStoreUnderClothing(existing.Value, component)) + { + _popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)), + user, user); + return; + } + _inventorySystem.TryEquip(user, parent, component.ClothingUid.Value, component.Slot, triggerHandContact: true); + } + + // If the toggleable clothing was uneqipped, try to equip whats in the under clothing container + if (wasAttachedUnequipped && !TryEquipUnderClothing(user, parent, component)) + TryDropUnderClothing(component); + // END DeltaV } private void OnGetActions(EntityUid uid, ToggleableClothingComponent component, GetItemActionsEvent args) @@ -264,6 +287,7 @@ public sealed class ToggleableClothingSystem : EntitySystem private void OnInit(EntityUid uid, ToggleableClothingComponent component, ComponentInit args) { component.Container = _containerSystem.EnsureContainer(uid, component.ContainerId); + component.UnderClothingContainer = _containerSystem.EnsureContainer(uid, component.UnderClothingContainerId); // Wayfarer - Allow hats under toggleable helms! } /// diff --git a/Content.Shared/_DV/Clothing/Components/ToggleableClothingComponent.UnderClothing.cs b/Content.Shared/_DV/Clothing/Components/ToggleableClothingComponent.UnderClothing.cs new file mode 100644 index 0000000000..ace52162e6 --- /dev/null +++ b/Content.Shared/_DV/Clothing/Components/ToggleableClothingComponent.UnderClothing.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Containers; + +namespace Content.Shared.Clothing.Components; + +/// +/// Extends upstream's ToggleableClothingComponent. +/// +/// This portion of the ToggleableClothingComponent stores the clothing item under the toggled piece. +/// Currently only supports a single piece of clothing, but pretty much all entities with ToggleableClothing +/// are just hardsuit helmets. +/// +public sealed partial class ToggleableClothingComponent : Component +{ + public const string DefaultUnderneathClothingContainerId = "toggleable-under-clothing"; + + /// + /// The container ID of . + /// + [DataField, AutoNetworkedField] + public string UnderClothingContainerId = DefaultUnderneathClothingContainerId; + + /// + /// The container where the item that the toggled clothing replaced is put. + /// + [ViewVariables] + public ContainerSlot? UnderClothingContainer; +} diff --git a/Content.Shared/_DV/Clothing/EntitySystems/ToggleableClothingSystem.UnderClothing.cs b/Content.Shared/_DV/Clothing/EntitySystems/ToggleableClothingSystem.UnderClothing.cs new file mode 100644 index 0000000000..7da273dbb8 --- /dev/null +++ b/Content.Shared/_DV/Clothing/EntitySystems/ToggleableClothingSystem.UnderClothing.cs @@ -0,0 +1,94 @@ +using Content.Shared.Clothing.Components; + +namespace Content.Shared.Clothing.EntitySystems; + +/// +/// Extends upstream's ToggleableClothingSystem. +/// +/// Provides methods that store and re-equip clothing when toggleable clothing is put on or taken off. +/// Sidenote; god, I hate naming things. +/// +public sealed partial class ToggleableClothingSystem : EntitySystem +{ + /// + /// Tries to store clothing in + /// + /// The clothing to be stored. + /// The ToggleableClothingComponent to store the clothing in. + /// True if clothing can be inserted and was inserted. + private bool TryStoreUnderClothing(EntityUid clothing, ToggleableClothingComponent component) + { + if (component.UnderClothingContainer == null) + return false; + + // There is already something in there? Either way, return false because we + // expect one thing. + if (component.UnderClothingContainer.ContainedEntity.HasValue) + return false; + + return _containerSystem.Insert(clothing, component.UnderClothingContainer); + } + + /// + /// Tries to equip any stored clothing kept in . + /// + /// The person wearing the ToggleableClothing. + /// The ToggleableClothingComponent to check for an stored items. + /// True if something was equipped OR if there is nothing to equip. + private bool TryEquipUnderClothing(EntityUid actor, ToggleableClothingComponent component) + { + return TryEquipUnderClothing(actor, actor, component); + } + + /// + /// Tries to equip any stored clothing kept in . + /// + /// The person trying to equip the clothing. + /// The person who to equip the clothing on. + /// The ToggleableClothingComponent to check for an stored items. + /// True if something was equipped OR if there is nothing to equip. + private bool TryEquipUnderClothing(EntityUid actor, EntityUid target, ToggleableClothingComponent component) + { + // if there is no UnderClothingContainer, then why are we here? + if (component.UnderClothingContainer == null) + return true; + + // if nothing is contained so technically dropping nothing counts as a success + if (!component.UnderClothingContainer.ContainedEntity.HasValue) + return true; + + return _inventorySystem.TryEquip(actor, target, component.UnderClothingContainer.ContainedEntity.Value, component.Slot, force: true); + } + + /// + /// Tries to equip any stored clothing kept in . + /// + /// The person trying to equip the clothing. + /// The AttachedClothing of the ToggleableClothing to check for an stored items. + /// True if something was equipped OR if there is nothing to equip. + private bool TryEquipUnderClothing(EntityUid actor, AttachedClothingComponent component) + { + if (!TryComp(component.AttachedUid, out var toggleableComp)) + return false; + + return TryEquipUnderClothing(actor, toggleableComp); + } + + /// + /// Tries to drop any stored clothing kept in . + /// + /// The ToggleableClothingComponent that is holding the item to be dropped. + /// True if there is not an item to be dropped OR it was successfully dropped. + private bool TryDropUnderClothing(ToggleableClothingComponent component) + { + // if there is no UnderClothingContainer, then why are we here? + if (component.UnderClothingContainer == null) + return true; + + // if nothing is contained so technically dropping nothing counts as a success + if (!component.UnderClothingContainer.ContainedEntity.HasValue) + return true; + + return _containerSystem.TryRemoveFromContainer(component.UnderClothingContainer.ContainedEntity.Value); + } +} diff --git a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml index 16f44268a6..f02d7469ac 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/cloaks.yml @@ -162,6 +162,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: entity parent: ClothingNeckBase @@ -188,6 +189,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: TypingIndicatorClothing proto: moth @@ -207,6 +209,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: TypingIndicatorClothing proto: alien diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml index 1454804a31..ab1f9b37e1 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml @@ -100,6 +100,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing storagebase: !type:Container ents: [] @@ -137,6 +138,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: GroupExamine - type: Tag tags: @@ -201,6 +203,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing storagebase: !type:Container ents: [] diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 19bdf2cdc9..b2fbeb839c 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -1189,6 +1189,7 @@ - type: ContainerContainer # Delta V-Brings back clownsuit but make it make sense containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing #Mime Hardsuit - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml index a91c79ccd1..291171e6b1 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml @@ -60,6 +60,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: Tag tags: - CorgiWearable @@ -173,6 +174,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: Tag tags: - CorgiWearable diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 9c667347e2..b4cca82f6e 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -136,6 +136,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: ProtectedFromStepTriggers slots: WITHOUT_POCKET - type: Tag @@ -255,6 +256,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: ProtectedFromStepTriggers slots: WITHOUT_POCKET @@ -275,6 +277,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: Construction graph: ClothingOuterSuitIan node: suit @@ -308,6 +311,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing - type: entity parent: ClothingOuterSuitCarp diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml index 4084fefba4..7db489a3dc 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml @@ -50,6 +50,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} # DeltaV - Allow hats under toggleable clothing storagebase: !type:Container ents: [] diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml index a43d319a0a..c03e2a1c20 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml @@ -19,13 +19,8 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot - showEnts: false - occludes: true - ent: null + toggleable-under-clothing: !type:ContainerSlot {} storagebase: !type:Container - showEnts: false - occludes: true - ents: [] - type: entity parent: ClothingOuterHardsuitSyndie diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/wintercoats.yml index 51025c8e38..8e0f8f8189 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/wintercoats.yml @@ -43,14 +43,9 @@ clothingPrototype: ClothingHeadHatHoodWinterMailCarrier - type: ContainerContainer containers: - storagebase: !type:Container - showEnts: False - occludes: True - ents: [] toggleable-clothing: !type:ContainerSlot - showEnts: False - occludes: True - ent: null + toggleable-under-clothing: !type:ContainerSlot {} + storagebase: !type:Container - type: Sprite sprite: Nyanotrasen/Clothing/OuterClothing/WinterCoats/mail_winter_coat.rsi - type: Clothing diff --git a/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/softsuits.yml b/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/softsuits.yml index 3e413d14b7..239a08e38a 100644 --- a/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/softsuits.yml +++ b/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/softsuits.yml @@ -14,6 +14,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot { } + toggleable-under-clothing: !type:ContainerSlot {} - type: Armor modifiers: coefficients: @@ -96,6 +97,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} - type: Tag tags: - CorgiWearable diff --git a/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/wintercoats.yml index df67aba12a..9102e9d314 100644 --- a/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/_DV/Entities/Clothing/OuterClothing/wintercoats.yml @@ -22,6 +22,7 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + toggleable-under-clothing: !type:ContainerSlot {} storagebase: !type:Container ents: []