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>
This commit is contained in:
Vanessa 2026-01-25 10:31:55 -06:00 committed by GitHub
parent c9faffdfa5
commit ec041a0f22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 173 additions and 21 deletions

View File

@ -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<ContainerSlot>(uid, component.ContainerId);
component.UnderClothingContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, component.UnderClothingContainerId); // Wayfarer - Allow hats under toggleable helms!
}
/// <summary>

View File

@ -0,0 +1,27 @@
using Robust.Shared.Containers;
namespace Content.Shared.Clothing.Components;
/// <summary>
/// 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.
/// </summary>
public sealed partial class ToggleableClothingComponent : Component
{
public const string DefaultUnderneathClothingContainerId = "toggleable-under-clothing";
/// <summary>
/// The container ID of <see cref="UnderClothingContainer"/>.
/// </summary>
[DataField, AutoNetworkedField]
public string UnderClothingContainerId = DefaultUnderneathClothingContainerId;
/// <summary>
/// The container where the item that the toggled clothing replaced is put.
/// </summary>
[ViewVariables]
public ContainerSlot? UnderClothingContainer;
}

View File

@ -0,0 +1,94 @@
using Content.Shared.Clothing.Components;
namespace Content.Shared.Clothing.EntitySystems;
/// <summary>
/// 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.
/// </summary>
public sealed partial class ToggleableClothingSystem : EntitySystem
{
/// <summary>
/// Tries to store clothing in <see cref="ToggleableClothingComponent.UnderClothingContainer"/>
/// </summary>
/// <param name="clothing">The clothing to be stored.</param>
/// <param name="component">The ToggleableClothingComponent to store the clothing in.</param>
/// <returns>True if clothing can be inserted and was inserted.</returns>
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);
}
/// <summary>
/// Tries to equip any stored clothing kept in <see cref="ToggleableClothingComponent.UnderClothingContainer"/>.
/// </summary>
/// <param name="actor">The person wearing the ToggleableClothing.</param>
/// <param name="component">The ToggleableClothingComponent to check for an stored items.</param>
/// <returns>True if something was equipped OR if there is nothing to equip.</returns>
private bool TryEquipUnderClothing(EntityUid actor, ToggleableClothingComponent component)
{
return TryEquipUnderClothing(actor, actor, component);
}
/// <summary>
/// Tries to equip any stored clothing kept in <see cref="ToggleableClothingComponent.UnderClothingContainer"/>.
/// </summary>
/// <param name="actor">The person trying to equip the clothing.</param>
/// <param name="target">The person who to equip the clothing on.</param>
/// <param name="component">The ToggleableClothingComponent to check for an stored items.</param>
/// <returns>True if something was equipped OR if there is nothing to equip.</returns>
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);
}
/// <summary>
/// Tries to equip any stored clothing kept in <see cref="ToggleableClothingComponent.UnderClothingContainer"/>.
/// </summary>
/// <param name="actor">The person trying to equip the clothing.</param>
/// <param name="component">The AttachedClothing of the ToggleableClothing to check for an stored items.</param>
/// <returns>True if something was equipped OR if there is nothing to equip.</returns>
private bool TryEquipUnderClothing(EntityUid actor, AttachedClothingComponent component)
{
if (!TryComp<ToggleableClothingComponent>(component.AttachedUid, out var toggleableComp))
return false;
return TryEquipUnderClothing(actor, toggleableComp);
}
/// <summary>
/// Tries to drop any stored clothing kept in <see cref="ToggleableClothingComponent.UnderClothingContainer"/>.
/// </summary>
/// <param name="component">The ToggleableClothingComponent that is holding the item to be dropped.</param>
/// <returns>True if there is not an item to be dropped OR it was successfully dropped.</returns>
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);
}
}

View File

@ -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

View File

@ -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: []

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: []

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -22,6 +22,7 @@
- type: ContainerContainer
containers:
toggleable-clothing: !type:ContainerSlot {}
toggleable-under-clothing: !type:ContainerSlot {}
storagebase: !type:Container
ents: []