diff --git a/Content.Shared/Clothing/Components/AttachedClothingComponent.cs b/Content.Shared/Clothing/Components/AttachedClothingComponent.cs
index 2e3b965f97..c52c875952 100644
--- a/Content.Shared/Clothing/Components/AttachedClothingComponent.cs
+++ b/Content.Shared/Clothing/Components/AttachedClothingComponent.cs
@@ -1,4 +1,5 @@
using Content.Shared.Clothing.EntitySystems;
+using Robust.Shared.GameStates;
namespace Content.Shared.Clothing.Components;
@@ -9,12 +10,12 @@ namespace Content.Shared.Clothing.Components;
/// hardsuit helmets.
///
[Access(typeof(ToggleableClothingSystem))]
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AttachedClothingComponent : Component
{
///
/// The Id of the piece of clothing that this entity belongs to.
///
- [DataField("AttachedUid")]
- public EntityUid AttachedUid = default!;
+ [DataField, AutoNetworkedField]
+ public EntityUid AttachedUid;
}
diff --git a/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs b/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs
index b87cd3fee5..3053efe89a 100644
--- a/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs
+++ b/Content.Shared/Clothing/Components/ToggleableClothingComponent.cs
@@ -1,17 +1,17 @@
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Inventory;
using Robust.Shared.Containers;
+using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Clothing.Components;
///
-/// This component gives an item an action that will equip or un-equip some clothing. Intended for use with
-/// hardsuits and hardsuit helmets.
+/// This component gives an item an action that will equip or un-equip some clothing e.g. hardsuits and hardsuit helmets.
///
[Access(typeof(ToggleableClothingSystem))]
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ToggleableClothingComponent : Component
{
public const string DefaultClothingContainerId = "toggleable-clothing";
@@ -19,35 +19,35 @@ public sealed partial class ToggleableClothingComponent : Component
///
/// Action used to toggle the clothing on or off.
///
- [DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string Action = "ActionToggleSuitPiece";
+ [DataField, AutoNetworkedField]
+ public EntProtoId Action = "ActionToggleSuitPiece";
- [DataField("actionEntity")]
+ [DataField, AutoNetworkedField]
public EntityUid? ActionEntity;
///
/// Default clothing entity prototype to spawn into the clothing container.
///
- [DataField("clothingPrototype", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))]
- public string ClothingPrototype = default!;
+ [DataField(required: true), AutoNetworkedField]
+ public EntProtoId ClothingPrototype = default!;
///
/// The inventory slot that the clothing is equipped to.
///
[ViewVariables(VVAccess.ReadWrite)]
- [DataField("slot")]
+ [DataField, AutoNetworkedField]
public string Slot = "head";
///
/// The inventory slot flags required for this component to function.
///
- [DataField("requiredSlot")]
+ [DataField("requiredSlot"), AutoNetworkedField]
public SlotFlags RequiredFlags = SlotFlags.OUTERCLOTHING;
///
/// The container that the clothing is stored in when not equipped.
///
- [DataField("containerId")]
+ [DataField, AutoNetworkedField]
public string ContainerId = DefaultClothingContainerId;
[ViewVariables]
@@ -57,18 +57,18 @@ public sealed partial class ToggleableClothingComponent : Component
/// The Id of the piece of clothing that belongs to this component. Required for map-saving if the clothing is
/// currently not inside of the container.
///
- [DataField("clothingUid")]
+ [DataField, AutoNetworkedField]
public EntityUid? ClothingUid;
///
/// Time it takes for this clothing to be toggled via the stripping menu verbs. Null prevents the verb from even showing up.
///
- [DataField("stripDelay")]
+ [DataField, AutoNetworkedField]
public TimeSpan? StripDelay = TimeSpan.FromSeconds(3);
///
/// Text shown in the toggle-clothing verb. Defaults to using the name of the action.
///
- [DataField("verbText")]
+ [DataField, AutoNetworkedField]
public string? VerbText;
}
diff --git a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
index ba006abfda..fa8c1af208 100644
--- a/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
+++ b/Content.Shared/Clothing/EntitySystems/ToggleableClothingSystem.cs
@@ -9,13 +9,17 @@ using Content.Shared.Popups;
using Content.Shared.Strip;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
+using Robust.Shared.Network;
using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Clothing.EntitySystems;
public sealed class ToggleableClothingSystem : EntitySystem
{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
@@ -24,8 +28,6 @@ public sealed class ToggleableClothingSystem : EntitySystem
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedStrippableSystem _strippable = default!;
- private Queue _toInsert = new();
-
public override void Initialize()
{
base.Initialize();
@@ -128,18 +130,6 @@ public sealed class ToggleableClothingSystem : EntitySystem
ToggleClothing(args.User, uid, component);
}
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- // process delayed insertions. Avoids doing a container insert during a container removal.
- while (_toInsert.TryDequeue(out var uid))
- {
- if (TryComp(uid, out ToggleableClothingComponent? component) && component.ClothingUid != null)
- component.Container?.Insert(component.ClothingUid.Value);
- }
- }
-
private void OnInteractHand(EntityUid uid, AttachedClothingComponent component, InteractHandEvent args)
{
if (args.Handled)
@@ -182,7 +172,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
_actionsSystem.RemoveAction(action.AttachedEntity.Value, component.ActionEntity);
}
- if (component.ClothingUid != null)
+ if (component.ClothingUid != null && !_netMan.IsClient)
QueueDel(component.ClothingUid.Value);
}
@@ -214,6 +204,10 @@ public sealed class ToggleableClothingSystem : EntitySystem
///
private void OnAttachedUnequip(EntityUid uid, AttachedClothingComponent component, GotUnequippedEvent args)
{
+ // Let containers worry about it.
+ if (_timing.ApplyingState)
+ return;
+
if (component.LifeStage > ComponentLifeStage.Running)
return;
@@ -225,7 +219,8 @@ public sealed class ToggleableClothingSystem : EntitySystem
// As unequipped gets called in the middle of container removal, we cannot call a container-insert without causing issues.
// So we delay it and process it during a system update:
- _toInsert.Enqueue(component.AttachedUid);
+ if (toggleComp.ClothingUid != null)
+ toggleComp.Container?.Insert(toggleComp.ClothingUid.Value);
}
///
@@ -250,7 +245,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
_inventorySystem.TryUnequip(user, parent, component.Slot);
else if (_inventorySystem.TryGetSlotEntity(parent, component.Slot, out var existing))
{
- _popupSystem.PopupEntity(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)),
+ _popupSystem.PopupClient(Loc.GetString("toggleable-clothing-remove-first", ("entity", existing)),
user, user);
}
else
@@ -294,8 +289,11 @@ public sealed class ToggleableClothingSystem : EntitySystem
{
var xform = Transform(uid);
component.ClothingUid = Spawn(component.ClothingPrototype, xform.Coordinates);
- EnsureComp(component.ClothingUid.Value).AttachedUid = uid;
+ var attachedClothing = EnsureComp(component.ClothingUid.Value);
+ attachedClothing.AttachedUid = uid;
+ Dirty(component.ClothingUid.Value, attachedClothing);
component.Container.Insert(component.ClothingUid.Value, EntityManager, ownerTransform: xform);
+ Dirty(uid, component);
}
if (_actionContainer.EnsureAction(uid, ref component.ActionEntity, out var action, component.Action))
diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs
index 44d9c5de3e..70083fbfeb 100644
--- a/Content.Shared/Inventory/InventorySystem.Equip.cs
+++ b/Content.Shared/Inventory/InventorySystem.Equip.cs
@@ -198,21 +198,8 @@ public abstract partial class InventorySystem
return false;
}
- if(!silent && clothing != null && clothing.EquipSound != null && _gameTiming.IsFirstTimePredicted)
+ if (!silent && clothing != null && clothing.EquipSound != null)
{
- Filter filter;
-
- if (_netMan.IsClient)
- filter = Filter.Local();
- else
- {
- filter = Filter.Pvs(target);
-
- // don't play double audio for predicted interactions
- if (predicted)
- filter.RemoveWhereAttachedEntity(entity => entity == actor);
- }
-
_audio.PlayPredicted(clothing.EquipSound, target, actor);
}
@@ -385,23 +372,13 @@ public abstract partial class InventorySystem
if (!slotContainer.Remove(removedItem.Value, force: force))
return false;
- _transform.DropNextTo(removedItem.Value, target);
+ // TODO: Inventory needs a hot cleanup hoo boy
+ // Check if something else (AKA toggleable) dumped it into a container.
+ if (!_containerSystem.IsEntityInContainer(removedItem.Value))
+ _transform.DropNextTo(removedItem.Value, target);
- if (!silent && Resolve(removedItem.Value, ref clothing, false) && clothing.UnequipSound != null && _gameTiming.IsFirstTimePredicted)
+ if (!silent && Resolve(removedItem.Value, ref clothing, false) && clothing.UnequipSound != null)
{
- Filter filter;
-
- if (_netMan.IsClient)
- filter = Filter.Local();
- else
- {
- filter = Filter.Pvs(target);
-
- // don't play double audio for predicted interactions
- if (predicted)
- filter.RemoveWhereAttachedEntity(entity => entity == actor);
- }
-
_audio.PlayPredicted(clothing.UnequipSound, target, actor);
}