Port carrying & pseudo-item tweaks & features from frontier

* Fix dropping carried entity when walking to a different grid and accidental escape from carrying

* Fix offset carrying

Better fix #1

* Add cancel escape action

* Add a popup shown when you're getting picked up

* Ported bag-carrying stuff (taking someone out of a bag makes you carry them, also allows to insert the carried person into a bag)

* Add sleeping inside bags

* Fix carrying mass contest

* Add the missing file

---------

Co-authored-by: Azzy <azzydev@icloud.com>
This commit is contained in:
Mnemotechnican 2024-05-06 21:21:07 +03:00 committed by GitHub
parent f32e4f67e7
commit 1abf205f76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 243 additions and 20 deletions

View File

@ -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.Hands.Systems;
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;
@ -22,11 +24,14 @@ using Content.Shared.Pulling;
using Content.Shared.Standing;
using Content.Shared.ActionBlocker;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Item;
using Content.Shared.Throwing;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Events;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Nyanotrasen.Item.PseudoItem;
using Content.Shared.Storage;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
@ -45,11 +50,13 @@ namespace Content.Server.Carrying
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
[Dependency] private readonly RespiratorSystem _respirator = default!;
[Dependency] private readonly PseudoItemSystem _pseudoItem = default!; // Needed for fitting check
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CarriableComponent, GetVerbsEvent<AlternativeVerb>>(AddCarryVerb);
SubscribeLocalEvent<CarryingComponent, GetVerbsEvent<InnateVerb>>(AddInsertCarriedVerb);
SubscribeLocalEvent<CarryingComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<CarryingComponent, BeforeThrowEvent>(OnThrow);
SubscribeLocalEvent<CarryingComponent, EntParentChangedMessage>(OnParentChanged);
@ -65,7 +72,6 @@ namespace Content.Server.Carrying
SubscribeLocalEvent<CarriableComponent, CarryDoAfterEvent>(OnDoAfter);
}
private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
@ -98,6 +104,33 @@ namespace Content.Server.Carrying
args.Verbs.Add(verb);
}
private void AddInsertCarriedVerb(EntityUid uid, CarryingComponent component, GetVerbsEvent<InnateVerb> 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<PseudoItemComponent>(toInsert, out var pseudoItem))
return;
if (!TryComp<StorageComponent>(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);
}
/// <summary>
/// Since the carried entity is stored as 2 virtual items, when deleted we want to drop them.
/// </summary>
@ -126,7 +159,12 @@ namespace Content.Server.Carrying
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);
@ -159,9 +197,13 @@ namespace Content.Server.Carrying
if (!TryComp<CanEscapeInventoryComponent>(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));
}
}
@ -210,12 +252,7 @@ namespace Content.Server.Carrying
}
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 @@ namespace Content.Server.Carrying
};
_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 @@ namespace Content.Server.Carrying
_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<BeingCarriedComponent>(carrier) || HasComp<ItemComponent>(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<CarryingComponent>(carrier); // get rid of this first so we don't recusrively fire that event
@ -323,5 +383,43 @@ namespace Content.Server.Carrying
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<BeingCarriedComponent>();
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();
}
}
}

View File

@ -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<PseudoItemComponent, GetVerbsEvent<AlternativeVerb>>(AddInsertAltVerb);
SubscribeLocalEvent<PseudoItemComponent, TryingToSleepEvent>(OnTrySleeping);
}
private void AddInsertAltVerb(EntityUid uid, PseudoItemComponent component, GetVerbsEvent<AlternativeVerb> args)
@ -53,4 +58,25 @@ public sealed class PseudoItemSystem : SharedPseudoItemSystem
};
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<SleepingComponent>(uid) && parent is { Valid: true } && HasComp<AllowsSleepInsideComponent>(parent))
_popup.PopupEntity(Loc.GetString("popup-sleep-in-bag", ("entity", uid)), uid);
}
}

View File

@ -15,4 +15,10 @@ public sealed partial class CanEscapeInventoryComponent : Component
[DataField("doAfter")]
public DoAfterId? DoAfter;
/// <summary>
/// DeltaV - action to cancel inventory escape. Added dynamically.
/// </summary>
[DataField]
public EntityUid? EscapeCancelAction;
}

View File

@ -5,6 +5,7 @@ using Content.Shared.Inventory;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.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.Movement.Events;
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!; // DeltaV
/// <summary>
/// You can't escape the hands of an entity this many times more massive than you.
/// </summary>
public const float MaximumMassDisadvantage = 6f;
/// <summary>
/// DeltaV - action to cancel inventory escape
/// </summary>
[ValidatePrototypeId<EntityPrototype>]
private readonly string _escapeCancelAction = "ActionCancelEscape";
public override void Initialize()
{
@ -37,6 +45,7 @@ public sealed class EscapeInventorySystem : EntitySystem
SubscribeLocalEvent<CanEscapeInventoryComponent, MoveInputEvent>(OnRelayMovement);
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeInventoryEvent>(OnEscape);
SubscribeLocalEvent<CanEscapeInventoryComponent, DroppedEvent>(OnDropped);
SubscribeLocalEvent<CanEscapeInventoryComponent, EscapeInventoryCancelActionEvent>(OnCancelEscape); // DeltaV
}
private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent component, ref MoveInputEvent args)
@ -83,12 +92,20 @@ public sealed class EscapeInventorySystem : EntitySystem
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting"), user, user);
_popupSystem.PopupEntity(Loc.GetString("escape-inventory-component-start-resisting-target"), container, container);
// DeltaV - 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;
// DeltaV - remove cancel action regardless of do-after result
_actions.RemoveAction(uid, component.EscapeCancelAction);
component.EscapeCancelAction = null;
if (args.Handled || args.Cancelled)
return;
@ -108,4 +125,14 @@ public sealed class EscapeInventorySystem : EntitySystem
if (component.DoAfter != null)
_doAfterSystem.Cancel(component.DoAfter);
}
// DeltaV
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;
}
}

View File

@ -0,0 +1,9 @@
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
/// <summary>
/// Signifies that pseudo-item creatures can sleep inside the container to which this component is applied.
/// </summary>
[RegisterComponent]
public sealed partial class AllowsSleepInsideComponent : Component
{
}

View File

@ -3,10 +3,10 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
/// <summary>
/// For entities that behave like an item under certain conditions,
/// but not under most conditions.
/// </summary>
/// <summary>
/// For entities that behave like an item under certain conditions,
/// but not under most conditions.
/// </summary>
[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;
/// <summary>
/// Action for sleeping while inside a container with <see cref="AllowsSleepInsideComponent"/>.
/// </summary>
[DataField]
public EntityUid? SleepAction;
}

View File

@ -11,7 +11,7 @@ namespace Content.Shared.Nyanotrasen.Item.PseudoItem;
/// </summary>
public partial class SharedPseudoItemSystem
{
protected bool CheckItemFits(Entity<PseudoItemComponent?> itemEnt, Entity<StorageComponent?> storageEnt)
public bool CheckItemFits(Entity<PseudoItemComponent?> itemEnt, Entity<StorageComponent?> storageEnt)
{
if (!Resolve(itemEnt, ref itemEnt.Comp) || !Resolve(storageEnt, ref storageEnt.Comp))
return false;

View File

@ -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<TagPrototype>]
private const string PreventTag = "PreventLabel";
[ValidatePrototypeId<EntityPrototype>]
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 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
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 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
return false;
}
// If the storage allows sleeping inside, add the respective action
if (HasComp<AllowsSleepInsideComponent>(storageUid))
_actions.AddAction(toInsert, ref component.SleepAction, SleepActionId, toInsert);
component.Active = true;
return true;
}
@ -98,9 +110,11 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
RemComp<ItemComponent>(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)
@ -153,7 +167,11 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem
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)

View File

@ -0,0 +1,6 @@
using Content.Shared.Actions;
namespace Content.Shared.Resist;
// DeltaV
public sealed partial class EscapeInventoryCancelActionEvent : InstantActionEvent;

View File

@ -0,0 +1 @@
popup-sleep-in-bag = {THE($entity)} curls up and falls asleep.

View File

@ -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!

View File

@ -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: DeltaV/Actions/escapeinventory.rsi/cancel-escape.png
event: !type:EscapeInventoryCancelActionEvent
useDelay: 2

View File

@ -30,6 +30,7 @@
delay: 0.5
- type: ExplosionResistance
damageCoefficient: 0.9
- type: AllowsSleepInside # DeltaV - enable sleeping inside bags
- type: entity
parent: ClothingBackpack
@ -267,7 +268,7 @@
- type: Sprite
sprite: Clothing/Back/Backpacks/syndicate.rsi
- type: ExplosionResistance
damageCoefficient: 0.1
damageCoefficient: 0.1
#Special
- type: entity

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

View File

@ -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"
}
]
}