diff --git a/Content.Server/Cuffs/Components/CuffableComponent.cs b/Content.Server/Cuffs/Components/CuffableComponent.cs index f567ee9b0f..4db8cc7133 100644 --- a/Content.Server/Cuffs/Components/CuffableComponent.cs +++ b/Content.Server/Cuffs/Components/CuffableComponent.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Linq; using Content.Server.DoAfter; using Content.Server.Hands.Components; @@ -8,21 +6,17 @@ using Content.Shared.Alert; using Content.Shared.Cuffs.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; -using Content.Shared.Interaction.Helpers; using Content.Shared.Popups; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Log; -using Robust.Shared.Maths; using Robust.Shared.Player; -using Robust.Shared.ViewVariables; namespace Content.Server.Cuffs.Components { + [ByRefEvent] + public readonly struct CuffedStateChangeEvent {} + [RegisterComponent] [ComponentReference(typeof(SharedCuffableComponent))] public sealed class CuffableComponent : SharedCuffableComponent @@ -46,9 +40,6 @@ namespace Content.Server.Cuffs.Components [ViewVariables(VVAccess.ReadOnly)] public Container Container { get; set; } = default!; - // TODO: Make a component message - public event Action? OnCuffedStateChanged; - private bool _uncuffing; protected override void Initialize() @@ -115,17 +106,19 @@ namespace Content.Server.Cuffs.Components CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? ownerHands) && ownerHands.Hands.Count() > CuffedHandCount; _sysMan.GetEntitySystem().UpdateCanMove(Owner); - OnCuffedStateChanged?.Invoke(); + var ev = new CuffedStateChangeEvent(); + _entMan.EventBus.RaiseLocalEvent(Owner, ref ev); UpdateAlert(); UpdateHeldItems(); - Dirty(); + Dirty(_entMan); return true; } public void CuffedStateChanged() { UpdateAlert(); - OnCuffedStateChanged?.Invoke(); + var ev = new CuffedStateChangeEvent(); + _entMan.EventBus.RaiseLocalEvent(Owner, ref ev); } /// @@ -270,9 +263,10 @@ namespace Content.Server.Cuffs.Components CanStillInteract = _entMan.TryGetComponent(Owner, out HandsComponent? handsComponent) && handsComponent.SortedHands.Count() > CuffedHandCount; _sysMan.GetEntitySystem().UpdateCanMove(Owner); - OnCuffedStateChanged?.Invoke(); + var ev = new CuffedStateChangeEvent(); + _entMan.EventBus.RaiseLocalEvent(Owner, ref ev); UpdateAlert(); - Dirty(); + Dirty(_entMan); if (CuffedHandCount == 0) { diff --git a/Content.Server/Strip/StrippableComponent.cs b/Content.Server/Strip/StrippableComponent.cs index f40dab284f..860dfc3c72 100644 --- a/Content.Server/Strip/StrippableComponent.cs +++ b/Content.Server/Strip/StrippableComponent.cs @@ -1,18 +1,6 @@ using System.Threading; -using Content.Server.Cuffs.Components; -using Content.Server.DoAfter; -using Content.Server.Hands.Components; -using Content.Server.Inventory; -using Content.Server.UserInterface; using Content.Shared.DragDrop; -using Content.Shared.Hands.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction.Events; -using Content.Shared.Inventory; -using Content.Shared.Popups; using Content.Shared.Strip.Components; -using Robust.Server.GameObjects; -using Robust.Server.Player; namespace Content.Server.Strip { @@ -21,336 +9,20 @@ namespace Content.Server.Strip [Friend(typeof(StrippableSystem))] public sealed class StrippableComponent : SharedStrippableComponent { - [Dependency] private readonly IEntityManager _entities = default!; - [Dependency] private readonly IEntitySystemManager _sysMan = default!; - private StrippableSystem _strippableSystem = default!; - - public const float StripDelay = 6f; - - // TODO: This component needs localization. + [ViewVariables] + [DataField("openDelay")] + public float OpenDelay = 4f; [ViewVariables] - public BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key); - - protected override void Initialize() - { - base.Initialize(); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += HandleUserInterfaceMessage; - } - - _strippableSystem = EntitySystem.Get(); - Owner.EnsureComponentWarn(); - if(_entities.TryGetComponent(Owner, out var cuffed)) - cuffed.OnCuffedStateChanged += UpdateState; - } - - protected override void Shutdown() - { - base.Shutdown(); - - if(_entities.TryGetComponent(Owner, out var cuffed)) - cuffed.OnCuffedStateChanged -= UpdateState; - } - - private void UpdateState() - { - _strippableSystem.SendUpdate(Owner, this); - } + [DataField("delay")] + public float StripDelay = 2f; public override bool Drop(DragDropEvent args) { - if (!_entities.TryGetComponent(args.User, out ActorComponent? actor)) return false; - - OpenUserInterface(actor.PlayerSession); + IoCManager.Resolve().GetEntitySystem().StartOpeningStripper(args.User, this); return true; } - public void OpenUserInterface(IPlayerSession session) - { - UserInterface?.Open(session); - } - - /// - /// Places item in user's active hand to an inventory slot. - /// - private async void PlaceActiveHandItemInInventory(EntityUid user, string slot) - { - var userHands = _entities.GetComponent(user); - var invSystem = _sysMan.GetEntitySystem(); - var handSys = _sysMan.GetEntitySystem(); - - bool Check() - { - if (userHands.ActiveHand?.HeldEntity is not EntityUid held) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); - return false; - } - - if (!handSys.CanDropHeld(user, userHands.ActiveHand)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); - return false; - } - - if (!invSystem.HasSlot(Owner, slot)) - return false; - - if (invSystem.TryGetSlotEntity(Owner, slot, out _)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-occupied",("owner", Owner))); - return false; - } - - if (!invSystem.CanEquip(user, Owner, held, slot, out _)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-equip-message",("owner", Owner))); - return false; - } - - return true; - } - - var doAfterSystem = EntitySystem.Get(); - - var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) - { - ExtraCheck = Check, - BreakOnStun = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - NeedHand = true, - }; - - var result = await doAfterSystem.WaitDoAfter(doAfterArgs); - if (result != DoAfterStatus.Finished) return; - - if (userHands.ActiveHand?.HeldEntity is EntityUid held - && handSys.TryDrop(user, userHands.ActiveHand, handsComp: userHands)) - { - invSystem.TryEquip(user, Owner, held, slot); - } - - UpdateState(); - } - - /// - /// Places item in user's active hand in one of the entity's hands. - /// - private async void PlaceActiveHandItemInHands(EntityUid user, string handName) - { - var hands = _entities.GetComponent(Owner); - var userHands = _entities.GetComponent(user); - var sys = _sysMan.GetEntitySystem(); - - bool Check() - { - if (userHands.ActiveHandEntity == null) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); - return false; - } - - if (!sys.CanDropHeld(user, userHands.ActiveHand!)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); - return false; - } - - if (!hands.Hands.TryGetValue(handName, out var hand) - || !sys.CanPickupToHand(Owner, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-put-message",("owner", Owner))); - return false; - } - - return true; - } - - var doAfterSystem = _sysMan.GetEntitySystem(); - - var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) - { - ExtraCheck = Check, - BreakOnStun = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - NeedHand = true, - }; - - var result = await doAfterSystem.WaitDoAfter(doAfterArgs); - if (result != DoAfterStatus.Finished) return; - - if (userHands.ActiveHandEntity is not EntityUid held) - return; - - sys.TryDrop(user, checkActionBlocker: false, handsComp: userHands); - sys.TryPickup(Owner, held, handName, checkActionBlocker: false, animateUser: true, handsComp: hands); - // hand update will trigger strippable update - } - - /// - /// Takes an item from the inventory and places it in the user's active hand. - /// - private async void TakeItemFromInventory(EntityUid user, string slot) - { - var inventory = _entities.GetComponent(Owner); - var userHands = _entities.GetComponent(user); - var invSystem = _sysMan.GetEntitySystem(); - - bool Check() - { - if (!invSystem.HasSlot(Owner, slot)) - return false; - - if (!invSystem.TryGetSlotEntity(Owner, slot, out var item)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", Owner))); - return false; - } - - if (!invSystem.CanUnequip(user, Owner, slot, out _)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-unequip-message",("owner", Owner))); - return false; - } - - return true; - } - - var doAfterSystem = _sysMan.GetEntitySystem(); - - var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) - { - ExtraCheck = Check, - BreakOnStun = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - }; - - var result = await doAfterSystem.WaitDoAfter(doAfterArgs); - if (result != DoAfterStatus.Finished) return; - - if (invSystem.TryGetSlotEntity(Owner, slot, out var item) && invSystem.TryUnequip(user, Owner, slot)) - { - // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping - _entities.EventBus.RaiseLocalEvent(item.Value, new DroppedEvent(user)); - - _sysMan.GetEntitySystem().PickupOrDrop(user, item.Value); - } - - UpdateState(); - } - - /// - /// Takes an item from a hand and places it in the user's active hand. - /// - private async void TakeItemFromHands(EntityUid user, string handName) - { - var hands = _entities.GetComponent(Owner); - var userHands = _entities.GetComponent(user); - var handSys = _sysMan.GetEntitySystem(); - - bool Check() - { - if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity == null) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message",("owner", Owner))); - return false; - } - - if (_entities.HasComponent(hand.HeldEntity)) - return false; - - if (!handSys.CanDropHeld(Owner, hand, false)) - { - user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop-message",("owner", Owner))); - return false; - } - - return true; - } - - var doAfterSystem = _sysMan.GetEntitySystem(); - - var doAfterArgs = new DoAfterEventArgs(user, StripDelay, CancellationToken.None, Owner) - { - ExtraCheck = Check, - BreakOnStun = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - BreakOnUserMove = true, - }; - - var result = await doAfterSystem.WaitDoAfter(doAfterArgs); - if (result != DoAfterStatus.Finished) return; - - if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity is not EntityUid held) - return; - - handSys.TryDrop(Owner, hand, checkActionBlocker: false, handsComp: hands); - handSys.PickupOrDrop(user, held, handsComp: userHands); - // hand update will trigger strippable update - } - - private void HandleUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) - { - if (obj.Session.AttachedEntity is not {Valid: true} user || - !_entities.TryGetComponent(user, out HandsComponent? userHands)) - return; - - var placingItem = userHands.ActiveHandEntity != null; - - switch (obj.Message) - { - case StrippingInventoryButtonPressed inventoryMessage: - if (_entities.TryGetComponent(Owner, out var inventory)) - { - if (_sysMan.GetEntitySystem().TryGetSlotEntity(Owner, inventoryMessage.Slot, out _, inventory)) - placingItem = false; - - if (placingItem) - PlaceActiveHandItemInInventory(user, inventoryMessage.Slot); - else - TakeItemFromInventory(user, inventoryMessage.Slot); - } - break; - - case StrippingHandButtonPressed handMessage: - - if (_entities.TryGetComponent(Owner, out var hands)) - { - if (hands.Hands.TryGetValue(handMessage.Hand, out var hand) && !hand.IsEmpty) - placingItem = false; - - if (placingItem) - PlaceActiveHandItemInHands(user, handMessage.Hand); - else - TakeItemFromHands(user, handMessage.Hand); - } - break; - - case StrippingHandcuffButtonPressed handcuffMessage: - - if (_entities.TryGetComponent(Owner, out var cuffed)) - { - foreach (var entity in cuffed.StoredEntities) - { - if (entity == handcuffMessage.Handcuff) - { - cuffed.TryUncuff(user, entity); - return; - } - } - } - break; - } - } + public Dictionary CancelTokens = new(); } } diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 49d4c0a8a1..7d32128f42 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -1,21 +1,29 @@ -using System.Collections.Generic; +using System.Threading; using Content.Server.Cuffs.Components; +using Content.Server.DoAfter; using Content.Server.Hands.Components; +using Content.Server.Inventory; +using Content.Server.UserInterface; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction.Events; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; +using Content.Shared.Popups; using Content.Shared.Strip.Components; using Content.Shared.Verbs; using Robust.Server.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; namespace Content.Server.Strip { public sealed class StrippableSystem : EntitySystem { + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + + // TODO: ECS popups. Not all of these have ECS equivalents yet. + public override void Initialize() { base.Initialize(); @@ -24,13 +32,125 @@ namespace Content.Server.Strip SubscribeLocalEvent(OnDidEquip); SubscribeLocalEvent(OnDidUnequip); SubscribeLocalEvent(OnCompInit); + SubscribeLocalEvent(OnCuffStateChange); + + // BUI + SubscribeLocalEvent(OnStripInvButtonMessage); + SubscribeLocalEvent(OnStripHandMessage); + SubscribeLocalEvent(OnStripHandcuffMessage); + + SubscribeLocalEvent(OnOpenStripComplete); + SubscribeLocalEvent(OnOpenStripCancelled); + } + + private void OnOpenStripCancelled(EntityUid uid, StrippableComponent component, OpenStrippingCancelledEvent args) + { + component.CancelTokens.Remove(args.User); + } + + private void OnOpenStripComplete(EntityUid uid, StrippableComponent component, OpenStrippingCompleteEvent args) + { + component.CancelTokens.Remove(args.User); + + if (!TryComp(args.User, out var actor)) return; + + uid.GetUIOrNull(StrippingUiKey.Key)?.Open(actor.PlayerSession); + } + + private void OnStripHandcuffMessage(EntityUid uid, StrippableComponent component, StrippingHandcuffButtonPressed args) + { + if (args.Session.AttachedEntity is not {Valid: true} user) + return; + + if (TryComp(component.Owner, out var cuffed)) + { + foreach (var entity in cuffed.StoredEntities) + { + if (entity != args.Handcuff) continue; + cuffed.TryUncuff(user, entity); + return; + } + } + } + + private void OnStripHandMessage(EntityUid uid, StrippableComponent component, StrippingHandButtonPressed args) + { + if (args.Session.AttachedEntity is not {Valid: true} user || + !TryComp(user, out var userHands)) + return; + + var placingItem = userHands.ActiveHandEntity != null; + + if (TryComp(component.Owner, out var hands)) + { + if (hands.Hands.TryGetValue(args.Hand, out var hand) && !hand.IsEmpty) + placingItem = false; + + if (placingItem) + PlaceActiveHandItemInHands(user, args.Hand, component); + else + TakeItemFromHands(user, args.Hand, component); + } + } + + private void OnStripInvButtonMessage(EntityUid uid, StrippableComponent component, StrippingInventoryButtonPressed args) + { + if (args.Session.AttachedEntity is not {Valid: true} user || + !TryComp(user, out var userHands)) + return; + + var placingItem = userHands.ActiveHandEntity != null; + + if (TryComp(component.Owner, out var inventory)) + { + if (_inventorySystem.TryGetSlotEntity(component.Owner, args.Slot, out _, inventory)) + placingItem = false; + + if (placingItem) + PlaceActiveHandItemInInventory(user, args.Slot, component); + else + TakeItemFromInventory(user, args.Slot, component); + } + } + + public void StartOpeningStripper(EntityUid user, StrippableComponent component) + { + if (component.CancelTokens.ContainsKey(user)) return; + + if (TryComp(user, out var actor)) + { + if (component.Owner.GetUIOrNull(StrippingUiKey.Key)?.SessionHasOpen(actor.PlayerSession) == true) + return; + } + + var token = new CancellationTokenSource(); + + var doAfterArgs = new DoAfterEventArgs(user, component.OpenDelay, token.Token, component.Owner) + { + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true, + TargetCancelledEvent = new OpenStrippingCancelledEvent(user), + TargetFinishedEvent = new OpenStrippingCompleteEvent(user), + }; + + component.CancelTokens[user] = token; + _doAfterSystem.DoAfter(doAfterArgs); } private void OnCompInit(EntityUid uid, StrippableComponent component, ComponentInit args) { + EnsureComp(uid); SendUpdate(uid, component); } + private void OnCuffStateChange(EntityUid uid, StrippableComponent component, ref CuffedStateChangeEvent args) + { + UpdateState(uid, component); + } + private void OnDidUnequip(EntityUid uid, StrippableComponent component, DidUnequipEvent args) { SendUpdate(uid, component); @@ -43,7 +163,9 @@ namespace Content.Server.Strip public void SendUpdate(EntityUid uid, StrippableComponent? strippableComponent = null) { - if (!Resolve(uid, ref strippableComponent, false) || strippableComponent.UserInterface == null) + var bui = uid.GetUIOrNull(StrippingUiKey.Key); + + if (!Resolve(uid, ref strippableComponent, false) || bui == null) { return; } @@ -88,7 +210,7 @@ namespace Content.Server.Strip } } - strippableComponent.UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs)); + bui.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs)); } private void AddStripVerb(EntityUid uid, StrippableComponent component, GetVerbsEvent args) @@ -99,11 +221,248 @@ namespace Content.Server.Strip if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) return; - Verb verb = new(); - verb.Text = Loc.GetString("strip-verb-get-data-text"); - verb.IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png"; - verb.Act = () => component.OpenUserInterface(actor.PlayerSession); + Verb verb = new() + { + Text = Loc.GetString("strip-verb-get-data-text"), + IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png", + Act = () => StartOpeningStripper(args.User, component), + }; args.Verbs.Add(verb); } + + private void UpdateState(EntityUid uid, StrippableComponent component) + { + SendUpdate(uid, component); + } + + /// + /// Places item in user's active hand to an inventory slot. + /// + private async void PlaceActiveHandItemInInventory(EntityUid user, string slot, StrippableComponent component) + { + var userHands = Comp(user); + + bool Check() + { + if (userHands.ActiveHand?.HeldEntity is not { } held) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); + return false; + } + + if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); + return false; + } + + if (!_inventorySystem.HasSlot(component.Owner, slot)) + return false; + + if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-occupied",("uid", component.Owner))); + return false; + } + + if (!_inventorySystem.CanEquip(user, component.Owner, held, slot, out _)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-equip-message",("uid", component.Owner))); + return false; + } + + return true; + } + + var doAfterArgs = new DoAfterEventArgs(user, component.StripDelay, CancellationToken.None, component.Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true, + }; + + var result = await _doAfterSystem.WaitDoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + if (userHands.ActiveHand?.HeldEntity is { } held + && _handsSystem.TryDrop(user, userHands.ActiveHand, handsComp: userHands)) + { + _inventorySystem.TryEquip(user, component.Owner, held, slot); + } + + UpdateState(component.Owner, component); + } + + /// + /// Places item in user's active hand in one of the entity's hands. + /// + private async void PlaceActiveHandItemInHands(EntityUid user, string handName, StrippableComponent component) + { + var hands = Comp(component.Owner); + var userHands = Comp(user); + + bool Check() + { + if (userHands.ActiveHandEntity == null) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-not-holding-anything")); + return false; + } + + if (!_handsSystem.CanDropHeld(user, userHands.ActiveHand!)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop")); + return false; + } + + if (!hands.Hands.TryGetValue(handName, out var hand) + || !_handsSystem.CanPickupToHand(component.Owner, userHands.ActiveHandEntity.Value, hand, checkActionBlocker: false, hands)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-put-message",("uid", component.Owner))); + return false; + } + + return true; + } + + var doAfterArgs = new DoAfterEventArgs(user, component.StripDelay, CancellationToken.None, component.Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true, + }; + + var result = await _doAfterSystem.WaitDoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + if (userHands.ActiveHandEntity is not { } held) + return; + + _handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: userHands); + _handsSystem.TryPickup(component.Owner, held, handName, checkActionBlocker: false, animateUser: true, handsComp: hands); + // hand update will trigger strippable update + } + + /// + /// Takes an item from the inventory and places it in the user's active hand. + /// + private async void TakeItemFromInventory(EntityUid user, string slot, StrippableComponent component) + { + bool Check() + { + if (!_inventorySystem.HasSlot(component.Owner, slot)) + return false; + + if (!_inventorySystem.TryGetSlotEntity(component.Owner, slot, out _)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message", ("uid", component.Owner))); + return false; + } + + if (!_inventorySystem.CanUnequip(user, component.Owner, slot, out _)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-unequip-message", ("uid", component.Owner))); + return false; + } + + return true; + } + + var doAfterArgs = new DoAfterEventArgs(user, component.StripDelay, CancellationToken.None, component.Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + }; + + var result = await _doAfterSystem.WaitDoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + if (_inventorySystem.TryGetSlotEntity(component.Owner, slot, out var item) && _inventorySystem.TryUnequip(user, component.Owner, slot)) + { + // Raise a dropped event, so that things like gas tank internals properly deactivate when stripping + RaiseLocalEvent(item.Value, new DroppedEvent(user)); + + _handsSystem.PickupOrDrop(user, item.Value); + } + + UpdateState(component.Owner, component); + } + + /// + /// Takes an item from a hand and places it in the user's active hand. + /// + private async void TakeItemFromHands(EntityUid user, string handName, StrippableComponent component) + { + var hands = Comp(component.Owner); + var userHands = Comp(user); + + bool Check() + { + if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity == null) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-item-slot-free-message",("uid", component.Owner))); + return false; + } + + if (HasComp(hand.HeldEntity)) + return false; + + if (!_handsSystem.CanDropHeld(component.Owner, hand, false)) + { + user.PopupMessageCursor(Loc.GetString("strippable-component-cannot-drop-message",("uid", component.Owner))); + return false; + } + + return true; + } + + var doAfterArgs = new DoAfterEventArgs(user, component.StripDelay, CancellationToken.None, component.Owner) + { + ExtraCheck = Check, + BreakOnStun = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + }; + + var result = await _doAfterSystem.WaitDoAfter(doAfterArgs); + if (result != DoAfterStatus.Finished) return; + + if (!hands.Hands.TryGetValue(handName, out var hand) || hand.HeldEntity is not { } held) + return; + + _handsSystem.TryDrop(component.Owner, hand, checkActionBlocker: false, handsComp: hands); + _handsSystem.PickupOrDrop(user, held, handsComp: userHands); + // hand update will trigger strippable update + } + + private sealed class OpenStrippingCompleteEvent + { + public readonly EntityUid User; + + public OpenStrippingCompleteEvent(EntityUid user) + { + User = user; + } + } + + private sealed class OpenStrippingCancelledEvent + { + public readonly EntityUid User; + + public OpenStrippingCancelledEvent(EntityUid user) + { + User = user; + } + } } } diff --git a/Content.Shared/Strip/Components/SharedStrippableComponent.cs b/Content.Shared/Strip/Components/SharedStrippableComponent.cs index 43521309ea..f1b13dc60a 100644 --- a/Content.Shared/Strip/Components/SharedStrippableComponent.cs +++ b/Content.Shared/Strip/Components/SharedStrippableComponent.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using Content.Shared.ActionBlocker; +using Content.Shared.ActionBlocker; using Content.Shared.DragDrop; using Content.Shared.Hands.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Serialization; namespace Content.Shared.Strip.Components @@ -26,12 +22,12 @@ namespace Content.Shared.Strip.Components } public abstract bool Drop(DragDropEvent args); + } - [NetSerializable, Serializable] - public enum StrippingUiKey - { - Key, - } + [NetSerializable, Serializable] + public enum StrippingUiKey : byte + { + Key, } [NetSerializable, Serializable]