diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index 2e2209f2ab..973e9bd0cf 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -348,6 +348,7 @@ namespace Content.Client.Entry "InteractionPopup", "HealthAnalyzer", "Thirst", + "CanEscapeInventory", "Wires" }; } diff --git a/Content.Server/Resist/CanEscapeInventoryComponent.cs b/Content.Server/Resist/CanEscapeInventoryComponent.cs new file mode 100644 index 0000000000..9ca1099903 --- /dev/null +++ b/Content.Server/Resist/CanEscapeInventoryComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; +using Robust.Shared.Analyzers; +using System.Threading; + +namespace Content.Server.Resist; + +[RegisterComponent] +public sealed class CanEscapeInventoryComponent : Component +{ + /// + /// How long it takes to break out of storage. Default at 5 seconds. + /// + [ViewVariables] + [DataField("resistTime")] + public float ResistTime = 5f; + + /// + /// For quick exit if the player attempts to move while already resisting + /// + [ViewVariables] + public bool IsResisting = false; + + /// + /// Cancellation token used to cancel the DoAfter if the mob is removed before it's complete + /// + public CancellationTokenSource? CancelToken; +} diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs new file mode 100644 index 0000000000..923f92d3d2 --- /dev/null +++ b/Content.Server/Resist/EscapeInventorySystem.cs @@ -0,0 +1,83 @@ +using Content.Shared.Movement; +using Content.Server.DoAfter; +using Robust.Shared.Containers; +using Content.Server.Popups; +using Content.Shared.Movement.EntitySystems; +using Robust.Shared.Player; +using Content.Shared.Storage; +using Content.Shared.Inventory; +using Content.Shared.Hands.Components; + +namespace Content.Server.Resist; + +public sealed class EscapeInventorySystem : EntitySystem +{ + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRelayMovement); + SubscribeLocalEvent(OnMoveAttempt); + SubscribeLocalEvent(OnEscapeComplete); + SubscribeLocalEvent(OnEscapeFail); + } + + private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, RelayMoveInputEvent args) + { + //Prevents the user from creating multiple DoAfters if they're already resisting. + if (component.IsResisting == true) + return; + + if (_containerSystem.TryGetContainingContainer(uid, out var container) + && (HasComp(container.Owner) || HasComp(container.Owner) || HasComp(container.Owner))) + { + AttemptEscape(uid, container.Owner, component); + } + } + + private void OnMoveAttempt(EntityUid uid, CanEscapeInventoryComponent component, UpdateCanMoveEvent args) + { + if (_containerSystem.IsEntityOrParentInContainer(uid)) + args.Cancel(); + } + + private void AttemptEscape(EntityUid user, EntityUid container, CanEscapeInventoryComponent component) + { + component.CancelToken = new(); + var doAfterEventArgs = new DoAfterEventArgs(user, component.ResistTime, component.CancelToken.Token, container) + { + BreakOnTargetMove = false, + BreakOnUserMove = false, + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = false, + UserFinishedEvent = new EscapeDoAfterComplete(), + UserCancelledEvent = new EscapeDoAfterCancel(), + }; + + component.IsResisting = true; + _popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, Filter.Entities(user)); + _popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, Filter.Entities(container)); + _doAfterSystem.DoAfter(doAfterEventArgs); + } + + private void OnEscapeComplete(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterComplete ev) + { + //Drops the mob on the tile below the container + Transform(uid).AttachParentToContainerOrGrid(EntityManager); + component.IsResisting = false; + } + + private void OnEscapeFail(EntityUid uid, CanEscapeInventoryComponent component, EscapeDoAfterCancel ev) + { + component.IsResisting = false; + } + + private sealed class EscapeDoAfterComplete : EntityEventArgs { } + + private sealed class EscapeDoAfterCancel : EntityEventArgs { } +} diff --git a/Resources/Locale/en-US/resist/components/escape-inventory-component.ftl b/Resources/Locale/en-US/resist/components/escape-inventory-component.ftl new file mode 100644 index 0000000000..d5d681626f --- /dev/null +++ b/Resources/Locale/en-US/resist/components/escape-inventory-component.ftl @@ -0,0 +1,2 @@ +escape-inventory-component-start-resisting = You start struggling to escape! +escape-inventory-component-start-resisting-target = Something is struggling to get out of your inventory! diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index d71782d9df..28a831b69a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -747,6 +747,7 @@ - type: Bloodstream bloodMaxVolume: 50 - type: DiseaseCarrier #The other class lab animal and disease vector + - type: CanEscapeInventory - type: entity