diff --git a/Content.Shared/Clothing/Components/ClothingComponent.cs b/Content.Shared/Clothing/Components/ClothingComponent.cs index 321b06114a..0f4c7f68bf 100644 --- a/Content.Shared/Clothing/Components/ClothingComponent.cs +++ b/Content.Shared/Clothing/Components/ClothingComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Clothing.EntitySystems; +using Content.Shared.DoAfter; using Content.Shared.Inventory; using Robust.Shared.Audio; using Robust.Shared.GameStates; @@ -66,6 +67,12 @@ public sealed partial class ClothingComponent : Component public ClothingMask UnisexMask = ClothingMask.UniformFull; public string? InSlot; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan EquipDelay = TimeSpan.Zero; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan UnequipDelay = TimeSpan.Zero; } [Serializable, NetSerializable] @@ -85,3 +92,30 @@ public enum ClothingMask : byte UniformFull, UniformTop } + +[Serializable, NetSerializable] +public sealed partial class ClothingEquipDoAfterEvent : DoAfterEvent +{ + public string Slot; + + public ClothingEquipDoAfterEvent(string slot) + { + Slot = slot; + } + + public override DoAfterEvent Clone() => this; +} + +[Serializable, NetSerializable] +public sealed partial class ClothingUnequipDoAfterEvent : DoAfterEvent +{ + public string Slot; + + public ClothingUnequipDoAfterEvent(string slot) + { + Slot = slot; + } + + public override DoAfterEvent Clone() => this; +} + diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index da2a09313a..92e31cfd8e 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -7,7 +7,6 @@ using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Tag; -using Content.Shared.Timing; using Robust.Shared.GameStates; namespace Content.Shared.Clothing.EntitySystems; @@ -33,7 +32,11 @@ public abstract class ClothingSystem : EntitySystem SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); SubscribeLocalEvent(OnMaskToggled); + + SubscribeLocalEvent(OnEquipDoAfter); + SubscribeLocalEvent(OnUnequipDoAfter); } + private void OnUseInHand(Entity ent, ref UseInHandEvent args) { if (args.Handled || !ent.Comp.QuickEquip) @@ -64,17 +67,17 @@ public abstract class ClothingSystem : EntitySystem if (TryComp(slotEntity, out ClothingComponent? item) && !item.QuickEquip) continue; - if (!_invSystem.TryUnequip(userEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt)) + if (!_invSystem.TryUnequip(userEnt, slotDef.Name, true, inventory: userEnt, checkDoafter: true)) continue; - if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt)) + if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt, checkDoafter: true)) continue; _handsSystem.PickupOrDrop(userEnt, slotEntity.Value, handsComp: userEnt); } else { - if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt)) + if (!_invSystem.TryEquip(userEnt, toEquipEnt, slotDef.Name, true, inventory: userEnt, clothing: toEquipEnt, checkDoafter: true)) continue; } @@ -113,6 +116,22 @@ public abstract class ClothingSystem : EntitySystem SetEquippedPrefix(ent, args.IsToggled ? "toggled" : null, ent); } + private void OnEquipDoAfter(Entity ent, ref ClothingEquipDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Target is not { } target) + return; + args.Handled = _invSystem.TryEquip(args.User, target, ent, args.Slot, clothing: ent.Comp, predicted: true, checkDoafter: false); + } + + private void OnUnequipDoAfter(Entity ent, ref ClothingUnequipDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Target is not { } target) + return; + args.Handled = _invSystem.TryUnequip(args.User, target, args.Slot, clothing: ent.Comp, predicted: true, checkDoafter: false); + if (args.Handled) + _handsSystem.TryPickup(args.User, ent); + } + #region Public API public void SetEquippedPrefix(EntityUid uid, string? prefix, ClothingComponent? clothing = null) diff --git a/Content.Shared/Interaction/SmartEquipSystem.cs b/Content.Shared/Interaction/SmartEquipSystem.cs index 17c8f2e511..fb2bc3c460 100644 --- a/Content.Shared/Interaction/SmartEquipSystem.cs +++ b/Content.Shared/Interaction/SmartEquipSystem.cs @@ -118,7 +118,7 @@ public sealed class SmartEquipSystem : EntitySystem } _hands.TryDrop(uid, hands.ActiveHand, handsComp: hands); - _inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true); + _inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true, checkDoafter:true); return; } @@ -209,7 +209,7 @@ public sealed class SmartEquipSystem : EntitySystem return; } - _inventory.TryUnequip(uid, equipmentSlot, inventory: inventory, predicted: true); + _inventory.TryUnequip(uid, equipmentSlot, inventory: inventory, predicted: true, checkDoafter: true); _hands.TryPickup(uid, slotItem, handsComp: hands); } } diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 5e740ec2ea..24006b0c9f 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Clothing.Components; +using Content.Shared.DoAfter; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -24,6 +25,7 @@ public abstract partial class InventorySystem [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; @@ -90,7 +92,7 @@ public abstract partial class InventorySystem // unequip the item. if (itemUid != null) { - if (!TryUnequip(actor, ev.Slot, out var item, predicted: true, inventory: inventory)) + if (!TryUnequip(actor, ev.Slot, out var item, predicted: true, inventory: inventory, checkDoafter: true)) return; _handsSystem.PickupOrDrop(actor, item.Value); @@ -114,15 +116,15 @@ public abstract partial class InventorySystem RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor), false); - TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory, force: true); + TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory, force: true, checkDoafter:true); } public bool TryEquip(EntityUid uid, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false, - InventoryComponent? inventory = null, ClothingComponent? clothing = null) => - TryEquip(uid, uid, itemUid, slot, silent, force, predicted, inventory, clothing); + InventoryComponent? inventory = null, ClothingComponent? clothing = null, bool checkDoafter = false) => + TryEquip(uid, uid, itemUid, slot, silent, force, predicted, inventory, clothing, checkDoafter); public bool TryEquip(EntityUid actor, EntityUid target, EntityUid itemUid, string slot, bool silent = false, bool force = false, bool predicted = false, - InventoryComponent? inventory = null, ClothingComponent? clothing = null) + InventoryComponent? inventory = null, ClothingComponent? clothing = null, bool checkDoafter = false) { if (!Resolve(target, ref inventory, false)) { @@ -149,6 +151,34 @@ public abstract partial class InventorySystem return false; } + if (checkDoafter && + clothing != null && + clothing.EquipDelay > TimeSpan.Zero && + (clothing.Slots & slotDefinition.SlotFlags) != 0 && + _containerSystem.CanInsert(itemUid, slotContainer)) + { + var args = new DoAfterArgs( + EntityManager, + actor, + clothing.EquipDelay, + new ClothingEquipDoAfterEvent(slot), + itemUid, + target, + itemUid) + { + BlockDuplicate = true, + BreakOnHandChange = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, + CancelDuplicate = true, + RequireCanInteract = true, + NeedHand = true + }; + + _doAfter.TryStartDoAfter(args); + return false; + } + if (!_containerSystem.Insert(itemUid, slotContainer)) { if(!silent && _gameTiming.IsFirstTimePredicted) @@ -156,7 +186,7 @@ public abstract partial class InventorySystem return false; } - if (!silent && clothing != null && clothing.EquipSound != null) + if (!silent && clothing != null) { _audio.PlayPredicted(clothing.EquipSound, target, actor); } @@ -284,9 +314,10 @@ public abstract partial class InventorySystem bool predicted = false, InventoryComponent? inventory = null, ClothingComponent? clothing = null, - bool reparent = true) + bool reparent = true, + bool checkDoafter = false) { - return TryUnequip(uid, uid, slot, silent, force, predicted, inventory, clothing, reparent); + return TryUnequip(uid, uid, slot, silent, force, predicted, inventory, clothing, reparent, checkDoafter); } public bool TryUnequip( @@ -298,9 +329,10 @@ public abstract partial class InventorySystem bool predicted = false, InventoryComponent? inventory = null, ClothingComponent? clothing = null, - bool reparent = true) + bool reparent = true, + bool checkDoafter = false) { - return TryUnequip(actor, target, slot, out _, silent, force, predicted, inventory, clothing, reparent); + return TryUnequip(actor, target, slot, out _, silent, force, predicted, inventory, clothing, reparent, checkDoafter); } public bool TryUnequip( @@ -312,9 +344,10 @@ public abstract partial class InventorySystem bool predicted = false, InventoryComponent? inventory = null, ClothingComponent? clothing = null, - bool reparent = true) + bool reparent = true, + bool checkDoafter = false) { - return TryUnequip(uid, uid, slot, out removedItem, silent, force, predicted, inventory, clothing, reparent); + return TryUnequip(uid, uid, slot, out removedItem, silent, force, predicted, inventory, clothing, reparent, checkDoafter); } public bool TryUnequip( @@ -327,7 +360,8 @@ public abstract partial class InventorySystem bool predicted = false, InventoryComponent? inventory = null, ClothingComponent? clothing = null, - bool reparent = true) + bool reparent = true, + bool checkDoafter = false) { removedItem = null; @@ -364,6 +398,33 @@ public abstract partial class InventorySystem if (!force && !_containerSystem.CanRemove(removedItem.Value, slotContainer)) return false; + if (checkDoafter && + Resolve(removedItem.Value, ref clothing, false) && + (clothing.Slots & slotDefinition.SlotFlags) != 0 && + clothing.UnequipDelay > TimeSpan.Zero) + { + var args = new DoAfterArgs( + EntityManager, + actor, + clothing.UnequipDelay, + new ClothingUnequipDoAfterEvent(slot), + removedItem.Value, + target, + removedItem.Value) + { + BlockDuplicate = true, + BreakOnHandChange = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, + CancelDuplicate = true, + RequireCanInteract = true, + NeedHand = true + }; + + _doAfter.TryStartDoAfter(args); + return false; + } + foreach (var slotDef in inventory.Slots) { if (slotDef != slotDefinition && slotDef.DependsOn == slotDefinition.Name)