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); }