From 58be8504857c54d311e9100ee89b921d2437ee35 Mon Sep 17 00:00:00 2001 From: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com> Date: Fri, 5 Jul 2024 20:28:44 +0300 Subject: [PATCH] Port All Carrying/PseudoItem/EscapeInventory Tweaks From DeltaV (#484) # Description This cherry-picks the following two PRs of mine from delta-v: - https://github.com/DeltaV-Station/Delta-v/pull/1118 (this one is a port of two other frontier PRs, see the original description for details) - https://github.com/DeltaV-Station/Delta-v/pull/1232 Encompassing a total of 8 distinct changes: 1. Fixes dropping the carried person when walking to a different grid (from station to shuttle or vice versa. Walking into space however will still make you drop them) and also makes sure that pressing shift while being carried does not make you escape. 2. Ensures that the carried person is always centered relative to the parent (under certain conditions, such as walking near a gravitational anomaly, their position can change, and that leads to really weird effects) 3. Fixes the mass contest in CarryingSystem that caused stronger entities to take longer to escape than weaker ones. 4. Adds popups for when you're getting picked up or being stuffed into a bag as a pseudo-item (e.g. a felinid) 5. Adds an action to stop escaping an inventory. This action gets added to your action bar when you attempt escaping and gets removed when you stop or escape. It applies both to carrying and items (hampsters, felinids, whatever else). 6. Adds a sleep action for pseudo-items stuffed inside a suitable bag. The bag must have a special component, which is added to the base backpack item and thus inherited by all soft bags (duffels, satchels, etc). Contrary to a popular belief, sleeping IS PURELY COSMETICAL and does not provide healing. (Beds provide healing when you buckle into them and that healing does not depend on whether or not you're sleeping) 7. Makes it so that when you try to take a pseudo-item out of the bag (e.g. a felinid), you automatically try to carry them (if you don't have enough free hands, they will be dropped on the floor like usually), and enables you to insert the carried person into a bag, but only if they're a pseudo-item (e.g. felinid). 8. Allows pseudoitems to be inserted into bags even when there are other items (as long as there's enough space) --- ## For technical details and video showcases, see the original PRs This PR is split into separate commits so different parts can be reverted if deemed unneccessary. --- # Changelog :cl: - fix: Carrying is less likely to behave erratically or suddenly interrupt now. - add: You can now see when someone is trying to pick you up, and also you can interrupt your attempt at escaping from their hands or inventory. - add: You can now properly take Felinids out of bags and place them inside. - add: Scientists have discovered that Felinids can sleep in bags. --- .../Nyanotrasen/Carrying/CarryingSystem.cs | 116 +++++++++++- .../Item/PseudoItem/PseudoItemSystem.cs | 30 +++- .../Resist/CanEscapeInventoryComponent.cs | 6 + .../Resist/EscapeInventorySystem.cs | 26 +++ .../PseudoItem/AllowsSleepInsideComponent.cs | 9 + .../Item/PseudoItem/PseudoItemComponent.cs | 14 +- .../SharedPseudoItemSystem.Checks.cs | 166 ++---------------- .../Item/PseudoItem/SharedPseudoItemSystem.cs | 24 ++- .../Resist/EscapeInventoryCancelEvent.cs | 5 + .../Locale/en-US/actions/actions/sleep.ftl | 2 + .../en-US/nyanotrasen/carrying/carry.ftl | 1 + Resources/Prototypes/Actions/misc.yml | 10 ++ .../Entities/Clothing/Back/backpacks.yml | 3 +- .../escapeinventory.rsi/cancel-escape.png | Bin 0 -> 559 bytes .../Actions/escapeinventory.rsi/meta.json | 14 ++ 15 files changed, 259 insertions(+), 167 deletions(-) create mode 100644 Content.Shared/Nyanotrasen/Item/PseudoItem/AllowsSleepInsideComponent.cs create mode 100644 Content.Shared/Resist/EscapeInventoryCancelEvent.cs create mode 100644 Resources/Prototypes/Actions/misc.yml create mode 100644 Resources/Textures/Actions/escapeinventory.rsi/cancel-escape.png create mode 100644 Resources/Textures/Actions/escapeinventory.rsi/meta.json diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs b/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs index bb071334fa1..103731b1b04 100644 --- a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs +++ b/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using System.Threading; using Content.Server.DoAfter; using Content.Server.Body.Systems; @@ -5,6 +6,7 @@ using Content.Server.Resist; using Content.Server.Popups; using Content.Server.Inventory; +using Content.Server.Nyanotrasen.Item.PseudoItem; using Content.Shared.Climbing; // Shared instead of Server using Content.Shared.Mobs; using Content.Shared.DoAfter; @@ -23,9 +25,12 @@ using Content.Shared.Standing; using Content.Shared.ActionBlocker; using Content.Shared.Inventory.VirtualItem; +using Content.Shared.Item; using Content.Shared.Throwing; using Content.Shared.Physics.Pull; using Content.Shared.Mobs.Systems; +using Content.Shared.Nyanotrasen.Item.PseudoItem; +using Content.Shared.Storage; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; @@ -44,11 +49,13 @@ public sealed class CarryingSystem : EntitySystem [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; [Dependency] private readonly RespiratorSystem _respirator = default!; + [Dependency] private readonly PseudoItemSystem _pseudoItem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent>(AddCarryVerb); + SubscribeLocalEvent>(AddInsertCarriedVerb); SubscribeLocalEvent(OnVirtualItemDeleted); SubscribeLocalEvent(OnThrow); SubscribeLocalEvent(OnParentChanged); @@ -64,7 +71,6 @@ public override void Initialize() SubscribeLocalEvent(OnDoAfter); } - private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent args) { if (!args.CanInteract || !args.CanAccess) @@ -97,6 +103,33 @@ private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsE args.Verbs.Add(verb); } + private void AddInsertCarriedVerb(EntityUid uid, CarryingComponent component, GetVerbsEvent args) + { + // If the person is carrying someone, and the carried person is a pseudo-item, and the target entity is a storage, + // then add an action to insert the carried entity into the target + var toInsert = args.Using; + if (toInsert is not { Valid: true } || !args.CanAccess || !TryComp(toInsert, out var pseudoItem)) + return; + + if (!TryComp(args.Target, out var storageComp)) + return; + + if (!_pseudoItem.CheckItemFits((toInsert.Value, pseudoItem), (args.Target, storageComp))) + return; + + InnateVerb verb = new() + { + Act = () => + { + DropCarried(uid, toInsert.Value); + _pseudoItem.TryInsert(args.Target, toInsert.Value, pseudoItem, storageComp); + }, + Text = Loc.GetString("action-name-insert-other", ("target", toInsert)), + Priority = 2 + }; + args.Verbs.Add(verb); + } + /// /// Since the carried entity is stored as 2 virtual items, when deleted we want to drop them. /// @@ -125,7 +158,12 @@ private void OnThrow(EntityUid uid, CarryingComponent component, BeforeThrowEven private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args) { - if (Transform(uid).MapID != args.OldMapId) + var xform = Transform(uid); + if (xform.MapID != args.OldMapId) + return; + + // Do not drop the carried entity if the new parent is a grid + if (xform.ParentUid == xform.GridUid) return; DropCarried(uid, component.Carried); @@ -158,9 +196,13 @@ private void OnMoveInput(EntityUid uid, BeingCarriedComponent component, ref Mov if (!TryComp(uid, out var escape)) return; + if (!args.HasDirectionalMovement) + return; + if (_actionBlockerSystem.CanInteract(uid, component.Carrier)) { - _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, MassContest(uid, component.Carrier)); + // Note: the mass contest is inverted because weaker entities are supposed to take longer to escape + _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, MassContest(component.Carrier, uid)); } } @@ -209,12 +251,7 @@ private void OnDoAfter(EntityUid uid, CarriableComponent component, CarryDoAfter } private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableComponent component) { - TimeSpan length = TimeSpan.FromSeconds(3); - - var mod = MassContest(carrier, carried); - - if (mod != 0) - length /= mod; + TimeSpan length = GetPickupDuration(carrier, carried); if (length >= TimeSpan.FromSeconds(9)) { @@ -236,6 +273,9 @@ private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableCo }; _doAfterSystem.TryStartDoAfter(args); + + // Show a popup to the person getting picked up + _popupSystem.PopupEntity(Loc.GetString("carry-started", ("carrier", carrier)), carried, carried); } private void Carry(EntityUid carrier, EntityUid carried) @@ -260,6 +300,26 @@ private void Carry(EntityUid carrier, EntityUid carried) _actionBlockerSystem.UpdateCanMove(carried); } + public bool TryCarry(EntityUid carrier, EntityUid toCarry, CarriableComponent? carriedComp = null) + { + if (!Resolve(toCarry, ref carriedComp, false)) + return false; + + if (!CanCarry(carrier, toCarry, carriedComp)) + return false; + + // The second one means that carrier is a pseudo-item and is inside a bag. + if (HasComp(carrier) || HasComp(carrier)) + return false; + + if (GetPickupDuration(carrier, toCarry) > TimeSpan.FromSeconds(9)) + return false; + + Carry(carrier, toCarry); + + return true; + } + public void DropCarried(EntityUid carrier, EntityUid carried) { RemComp(carrier); // get rid of this first so we don't recusrively fire that event @@ -323,5 +383,43 @@ private float MassContest(EntityUid roller, EntityUid target, PhysicsComponent? return rollerPhysics.FixturesMass / targetPhysics.FixturesMass; } + + private TimeSpan GetPickupDuration(EntityUid carrier, EntityUid carried) + { + var length = TimeSpan.FromSeconds(3); + + var mod = MassContest(carrier, carried); + if (mod != 0) + length /= mod; + + return length; + } + + public override void Update(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var carried, out var comp)) + { + var carrier = comp.Carrier; + if (carrier is not { Valid: true } || carried is not { Valid: true }) + continue; + + // SOMETIMES - when an entity is inserted into disposals, or a cryosleep chamber - it can get re-parented without a proper reparent event + // when this happens, it needs to be dropped because it leads to weird behavior + if (Transform(carried).ParentUid != carrier) + { + DropCarried(carrier, carried); + continue; + } + + // Make sure the carried entity is always centered relative to the carrier, as gravity pulls can offset it otherwise + var xform = Transform(carried); + if (!xform.LocalPosition.Equals(Vector2.Zero)) + { + xform.LocalPosition = Vector2.Zero; + } + } + query.Dispose(); + } } } diff --git a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs index 76cfe7d904b..6df387e6ba8 100644 --- a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs +++ b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs @@ -1,6 +1,9 @@ -using Content.Server.DoAfter; +using Content.Server.Carrying; +using Content.Server.DoAfter; using Content.Server.Item; +using Content.Server.Popups; using Content.Server.Storage.EntitySystems; +using Content.Shared.Bed.Sleep; using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Item; @@ -17,12 +20,14 @@ public sealed class PseudoItemSystem : SharedPseudoItemSystem [Dependency] private readonly StorageSystem _storage = default!; [Dependency] private readonly ItemSystem _item = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; - + [Dependency] private readonly CarryingSystem _carrying = default!; + [Dependency] private readonly PopupSystem _popup = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent>(AddInsertAltVerb); + SubscribeLocalEvent(OnTrySleeping); } private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent args) @@ -53,4 +58,25 @@ private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetV }; args.Verbs.Add(verb); } + + protected override void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args) + { + // Try to pick the entity up instead first + if (args.User != args.Item && _carrying.TryCarry(args.User, uid)) + { + args.Cancel(); + return; + } + + // If could not pick up, just take it out onto the ground as per default + base.OnGettingPickedUpAttempt(uid, component, args); + } + + // Show a popup when a pseudo-item falls asleep inside a bag. + private void OnTrySleeping(EntityUid uid, PseudoItemComponent component, TryingToSleepEvent args) + { + var parent = Transform(uid).ParentUid; + if (!HasComp(uid) && parent is { Valid: true } && HasComp(parent)) + _popup.PopupEntity(Loc.GetString("popup-sleep-in-bag", ("entity", uid)), uid); + } } diff --git a/Content.Server/Resist/CanEscapeInventoryComponent.cs b/Content.Server/Resist/CanEscapeInventoryComponent.cs index 19b4abf7d0c..978e03d95f9 100644 --- a/Content.Server/Resist/CanEscapeInventoryComponent.cs +++ b/Content.Server/Resist/CanEscapeInventoryComponent.cs @@ -15,4 +15,10 @@ public sealed partial class CanEscapeInventoryComponent : Component [DataField("doAfter")] public DoAfterId? DoAfter; + + /// + /// Action to cancel inventory escape. + /// + [DataField] + public EntityUid? EscapeCancelAction; } diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs index 127db7d2b34..95a470e9093 100644 --- a/Content.Server/Resist/EscapeInventorySystem.cs +++ b/Content.Server/Resist/EscapeInventorySystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Hands.EntitySystems; using Content.Server.Storage.Components; using Content.Shared.ActionBlocker; +using Content.Shared.Actions; using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Events; @@ -13,6 +14,7 @@ using Content.Shared.Resist; using Content.Shared.Storage; using Robust.Shared.Containers; +using Robust.Shared.Prototypes; namespace Content.Server.Resist; @@ -24,11 +26,17 @@ public sealed class EscapeInventorySystem : EntitySystem [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly CarryingSystem _carryingSystem = default!; // Carrying system from Nyanotrasen. + [Dependency] private readonly SharedActionsSystem _actions = default!; /// /// You can't escape the hands of an entity this many times more massive than you. /// public const float MaximumMassDisadvantage = 6f; + /// + /// Action to cancel inventory escape + /// + [ValidatePrototypeId] + private readonly string _escapeCancelAction = "ActionCancelEscape"; public override void Initialize() { @@ -37,6 +45,7 @@ public override void Initialize() SubscribeLocalEvent(OnRelayMovement); SubscribeLocalEvent(OnEscape); SubscribeLocalEvent(OnDropped); + SubscribeLocalEvent(OnCancelEscape); } private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args) @@ -84,12 +93,20 @@ private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent componen _popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, user); _popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, container); + + // Add an escape cancel action + if (component.EscapeCancelAction is not { Valid: true }) + _actions.AddAction(user, ref component.EscapeCancelAction, _escapeCancelAction); } private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, EscapeInventoryEvent args) { component.DoAfter = null; + // Remove the cancel action regardless of do-after result + _actions.RemoveAction(uid, component.EscapeCancelAction); + component.EscapeCancelAction = null; + if (args.Handled || args.Cancelled) return; @@ -109,4 +126,13 @@ private void OnDropped(EntityUid uid, CanEscapeInventoryComponent component, Dro if (component.DoAfter != null) _doAfterSystem.Cancel(component.DoAfter); } + + private void OnCancelEscape(EntityUid uid, CanEscapeInventoryComponent component, EscapeInventoryCancelActionEvent args) + { + if (component.DoAfter != null) + _doAfterSystem.Cancel(component.DoAfter); + + _actions.RemoveAction(uid, component.EscapeCancelAction); + component.EscapeCancelAction = null; + } } diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/AllowsSleepInsideComponent.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/AllowsSleepInsideComponent.cs new file mode 100644 index 00000000000..a28c7698fcd --- /dev/null +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/AllowsSleepInsideComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Nyanotrasen.Item.PseudoItem; + +/// +/// Signifies that pseudo-item creatures can sleep inside the container to which this component is applied. +/// +[RegisterComponent] +public sealed partial class AllowsSleepInsideComponent : Component +{ +} diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs index d3774439d36..458b514b969 100644 --- a/Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/PseudoItemComponent.cs @@ -3,10 +3,10 @@ namespace Content.Shared.Nyanotrasen.Item.PseudoItem; - /// - /// For entities that behave like an item under certain conditions, - /// but not under most conditions. - /// +/// +/// For entities that behave like an item under certain conditions, +/// but not under most conditions. +/// [RegisterComponent, AutoGenerateComponentState] public sealed partial class PseudoItemComponent : Component { @@ -24,4 +24,10 @@ public sealed partial class PseudoItemComponent : Component public Vector2i StoredOffset; public bool Active = false; + + /// + /// Action for sleeping while inside a container with . + /// + [DataField] + public EntityUid? SleepAction; } diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs index 7000c654048..906503b3707 100644 --- a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.Checks.cs @@ -3,163 +3,33 @@ namespace Content.Shared.Nyanotrasen.Item.PseudoItem; -/// -/// Almost all of this is code taken from other systems, but adapted to use PseudoItem. -/// I couldn't use the original functions because the resolve would fuck shit up, even if I passed a constructed itemcomp -/// -/// This is horrible, and I hate it. But such is life -/// public partial class SharedPseudoItemSystem { - protected bool CheckItemFits(Entity itemEnt, Entity storageEnt) + /// + /// Checks if the pseudo-item can be inserted into the specified storage entity. + /// + /// + /// This function creates and uses a fake item component if the entity doesn't have one. + /// + public bool CheckItemFits(Entity itemEnt, Entity storageEnt) { if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp)) return false; - if (Transform(itemEnt).Anchored) + if (!TryComp(itemEnt, out var metadata)) return false; - if (storageEnt.Comp.Whitelist?.IsValid(itemEnt, EntityManager) == false) - return false; - - if (storageEnt.Comp.Blacklist?.IsValid(itemEnt, EntityManager) == true) - return false; - - var maxSize = _storage.GetMaxItemSize(storageEnt); - if (_item.GetSizePrototype(itemEnt.Comp.Size) > maxSize) - return false; - - // The following is shitfucked together straight from TryGetAvailableGridSpace, but eh, it works - - var itemComp = new ItemComponent - { Size = itemEnt.Comp.Size, Shape = itemEnt.Comp.Shape, StoredOffset = itemEnt.Comp.StoredOffset }; - - var storageBounding = storageEnt.Comp.Grid.GetBoundingBox(); - - Angle startAngle; - if (storageEnt.Comp.DefaultStorageOrientation == null) - startAngle = Angle.FromDegrees(-itemComp.StoredRotation); // PseudoItem doesn't support this - else - { - if (storageBounding.Width < storageBounding.Height) - { - startAngle = storageEnt.Comp.DefaultStorageOrientation == StorageDefaultOrientation.Horizontal - ? Angle.Zero - : Angle.FromDegrees(90); - } - else - { - startAngle = storageEnt.Comp.DefaultStorageOrientation == StorageDefaultOrientation.Vertical - ? Angle.Zero - : Angle.FromDegrees(90); - } - } - - for (var y = storageBounding.Bottom; y <= storageBounding.Top; y++) - { - for (var x = storageBounding.Left; x <= storageBounding.Right; x++) - { - for (var angle = startAngle; angle <= Angle.FromDegrees(360 - startAngle); angle += Math.PI / 2f) - { - var location = new ItemStorageLocation(angle, (x, y)); - if (ItemFitsInGridLocation(itemEnt, storageEnt, location.Position, location.Rotation)) - return true; - } - } - } - - return false; - } - - private bool ItemFitsInGridLocation( - Entity itemEnt, - Entity storageEnt, - Vector2i position, - Angle rotation) - { - if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp)) - return false; - - var gridBounds = storageEnt.Comp.Grid.GetBoundingBox(); - if (!gridBounds.Contains(position)) - return false; - - var itemShape = GetAdjustedItemShape(itemEnt, rotation, position); - - foreach (var box in itemShape) + TryComp(itemEnt, out var item); + // If the entity doesn't have an item comp, create a fake one + // The fake component is never actually added to the entity + item ??= new ItemComponent { - for (var offsetY = box.Bottom; offsetY <= box.Top; offsetY++) - { - for (var offsetX = box.Left; offsetX <= box.Right; offsetX++) - { - var pos = (offsetX, offsetY); - - if (!IsGridSpaceEmpty(itemEnt, storageEnt, pos, itemShape)) - return false; - } - } - } - - return true; - } - - private IReadOnlyList GetAdjustedItemShape(Entity entity, Angle rotation, - Vector2i position) - { - if (!Resolve(entity, ref entity.Comp)) - return new Box2i[] { }; - - var shapes = entity.Comp.Shape ?? _item.GetSizePrototype(entity.Comp.Size).DefaultShape; - var boundingShape = shapes.GetBoundingBox(); - var boundingCenter = ((Box2) boundingShape).Center; - var matty = Matrix3.CreateTransform(boundingCenter, rotation); - var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft; - - var adjustedShapes = new List(); - foreach (var shape in shapes) - { - var transformed = matty.TransformBox(shape).Translated(drift); - var floored = new Box2i(transformed.BottomLeft.Floored(), transformed.TopRight.Floored()); - var translated = floored.Translated(position); - - adjustedShapes.Add(translated); - } - - return adjustedShapes; - } - - private bool IsGridSpaceEmpty(Entity itemEnt, Entity storageEnt, - Vector2i location, IReadOnlyList shape) - { - if (!Resolve(storageEnt, ref storageEnt.Comp)) - return false; - - var validGrid = false; - foreach (var grid in storageEnt.Comp.Grid) - { - if (grid.Contains(location)) - { - validGrid = true; - break; - } - } - - if (!validGrid) - return false; - - foreach (var (ent, storedItem) in storageEnt.Comp.StoredItems) - { - if (ent == itemEnt.Owner) - continue; - - var adjustedShape = shape; - foreach (var box in adjustedShape) - { - if (box.Contains(location)) - return false; - } - } + Owner = itemEnt, + Shape = itemEnt.Comp.Shape, + Size = itemEnt.Comp.Size, + StoredOffset = itemEnt.Comp.StoredOffset + }; - return true; + return _storage.CanInsert(storageEnt, itemEnt, out _, storageEnt.Comp, item, ignoreStacks: true); } } diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs index 4b7910746f1..5f4e6184346 100644 --- a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs @@ -1,14 +1,18 @@ +using Content.Shared.Actions; +using Content.Shared.Bed.Sleep; using Content.Shared.DoAfter; using Content.Shared.Hands; using Content.Shared.IdentityManagement; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Item.PseudoItem; +using Content.Shared.Popups; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.Containers; +using Robust.Shared.Prototypes; namespace Content.Shared.Nyanotrasen.Item.PseudoItem; @@ -18,9 +22,13 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; [ValidatePrototypeId] private const string PreventTag = "PreventLabel"; + [ValidatePrototypeId] + private const string SleepActionId = "ActionSleep"; // The action used for sleeping inside bags. Currently uses the default sleep action (same as beds) public override void Initialize() { @@ -64,7 +72,7 @@ private void AddInsertVerb(EntityUid uid, PseudoItemComponent component, GetVerb args.Verbs.Add(verb); } - private bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component, + public bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemComponent component, StorageComponent? storage = null) { if (!Resolve(storageUid, ref storage)) @@ -87,6 +95,10 @@ private bool TryInsert(EntityUid storageUid, EntityUid toInsert, PseudoItemCompo return false; } + // If the storage allows sleeping inside, add the respective action + if (HasComp(storageUid)) + _actions.AddAction(toInsert, ref component.SleepAction, SleepActionId, toInsert); + component.Active = true; return true; } @@ -98,9 +110,11 @@ private void OnEntRemoved(EntityUid uid, PseudoItemComponent component, EntGotRe RemComp(uid); component.Active = false; + + _actions.RemoveAction(uid, component.SleepAction); // Remove sleep action if it was added } - private void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, + protected virtual void OnGettingPickedUpAttempt(EntityUid uid, PseudoItemComponent component, GettingPickedUpAttemptEvent args) { if (args.User == args.Item) @@ -154,7 +168,11 @@ protected void StartInsertDoAfter(EntityUid inserter, EntityUid toInsert, Entity NeedHand = true }; - _doAfter.TryStartDoAfter(args); + if (_doAfter.TryStartDoAfter(args)) + { + // Show a popup to the person getting picked up + _popupSystem.PopupEntity(Loc.GetString("carry-started", ("carrier", inserter)), toInsert, toInsert); + } } private void OnAttackAttempt(EntityUid uid, PseudoItemComponent component, AttackAttemptEvent args) diff --git a/Content.Shared/Resist/EscapeInventoryCancelEvent.cs b/Content.Shared/Resist/EscapeInventoryCancelEvent.cs new file mode 100644 index 00000000000..75ee09ff045 --- /dev/null +++ b/Content.Shared/Resist/EscapeInventoryCancelEvent.cs @@ -0,0 +1,5 @@ +using Content.Shared.Actions; + +namespace Content.Shared.Resist; + +public sealed partial class EscapeInventoryCancelActionEvent : InstantActionEvent; diff --git a/Resources/Locale/en-US/actions/actions/sleep.ftl b/Resources/Locale/en-US/actions/actions/sleep.ftl index fd833fd4a5c..6188e1639fe 100644 --- a/Resources/Locale/en-US/actions/actions/sleep.ftl +++ b/Resources/Locale/en-US/actions/actions/sleep.ftl @@ -5,3 +5,5 @@ sleep-examined = [color=lightblue]{CAPITALIZE(SUBJECT($target))} {CONJUGATE-BE($ wake-other-success = You shake {THE($target)} awake. wake-other-failure = You shake {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} not waking up. + +popup-sleep-in-bag = {THE($entity)} curls up and falls asleep. diff --git a/Resources/Locale/en-US/nyanotrasen/carrying/carry.ftl b/Resources/Locale/en-US/nyanotrasen/carrying/carry.ftl index 4fa1abae8bd..490daced3f2 100644 --- a/Resources/Locale/en-US/nyanotrasen/carrying/carry.ftl +++ b/Resources/Locale/en-US/nyanotrasen/carrying/carry.ftl @@ -1,3 +1,4 @@ carry-verb = Carry carry-too-heavy = You're not strong enough. +carry-started = {THE($carrier)} is trying to pick you up! diff --git a/Resources/Prototypes/Actions/misc.yml b/Resources/Prototypes/Actions/misc.yml new file mode 100644 index 00000000000..60fec699210 --- /dev/null +++ b/Resources/Prototypes/Actions/misc.yml @@ -0,0 +1,10 @@ +- type: entity + id: ActionCancelEscape + name: Stop escaping + description: Calm down and sit peacefuly in your carrier's inventory + noSpawn: true + components: + - type: InstantAction + icon: Actions/escapeinventory.rsi/cancel-escape.png + event: !type:EscapeInventoryCancelActionEvent + useDelay: 2 diff --git a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml index fbd5a02fa08..d72006f6c41 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml @@ -30,6 +30,7 @@ delay: 0.5 - type: ExplosionResistance damageCoefficient: 0.9 + - type: AllowsSleepInside # DeltaV - enable sleeping inside bags - type: entity parent: ClothingBackpack @@ -258,7 +259,7 @@ - type: Sprite sprite: Clothing/Back/Backpacks/syndicate.rsi - type: ExplosionResistance - damageCoefficient: 0.1 + damageCoefficient: 0.1 #Special - type: entity diff --git a/Resources/Textures/Actions/escapeinventory.rsi/cancel-escape.png b/Resources/Textures/Actions/escapeinventory.rsi/cancel-escape.png new file mode 100644 index 0000000000000000000000000000000000000000..609e9e3d199149f20e44d2684167d14ec0353ee9 GIT binary patch literal 559 zcmV+~0?_@5P)C<~Jk;wDqh(aouDd;i};T#PBQ58a& zHyRDGDJ4mgYnmL_Nqgmd!})(FzRWWIEN{&&k(O4!}HkuiK@U*EcKAx@acl>3J9v x$Dd)UhGdTYLIJq2bd+Wom#K+8KmYF&_y*p|(%DtXaRmSX002ovPDHLkV1kRp{4oFk literal 0 HcmV?d00001 diff --git a/Resources/Textures/Actions/escapeinventory.rsi/meta.json b/Resources/Textures/Actions/escapeinventory.rsi/meta.json new file mode 100644 index 00000000000..ba379dedab4 --- /dev/null +++ b/Resources/Textures/Actions/escapeinventory.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Duffelbag icon taken from tgstation at commit https://github.com/tgstation/tgstation/commit/547852588166c8e091b441e4e67169e156bb09c1 | Modified into cancel-escape.png by Mnemotechnician (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "cancel-escape" + } + ] +}