diff --git a/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs b/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs
index ec800db2a30..079ec77bbf3 100644
--- a/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs
+++ b/Content.Server/DeltaV/GlimmerWisp/LifeDrainerSystem.cs
@@ -1,5 +1,6 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
+using Content.Shared.DeltaV.Carrying;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Systems;
@@ -11,7 +12,6 @@
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Content.Server.NPC.Components;
-using Content.Server.Carrying;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Utility;
diff --git a/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs b/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs
deleted file mode 100644
index afc78978c86..00000000000
--- a/Content.Server/Nyanotrasen/Carrying/BeingCarriedComponent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Content.Server.Carrying
-{
- ///
- /// Stores the carrier of an entity being carried.
- ///
- [RegisterComponent]
- public sealed partial class BeingCarriedComponent : Component
- {
- public EntityUid Carrier = default!;
- }
-}
diff --git a/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs b/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs
deleted file mode 100644
index f4fd1fa6d56..00000000000
--- a/Content.Server/Nyanotrasen/Carrying/CarriableComponent.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Threading;
-
-namespace Content.Server.Carrying
-{
- [RegisterComponent]
- public sealed partial class CarriableComponent : Component
- {
- ///
- /// Number of free hands required
- /// to carry the entity
- ///
- [DataField("freeHandsRequired")]
- public int FreeHandsRequired = 2;
-
- public CancellationTokenSource? CancelToken;
- }
-}
diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs b/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs
deleted file mode 100644
index e79460595b9..00000000000
--- a/Content.Server/Nyanotrasen/Carrying/CarryingComponent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Content.Server.Carrying
-{
- ///
- /// Added to an entity when they are carrying somebody.
- ///
- [RegisterComponent]
- public sealed partial class CarryingComponent : Component
- {
- public EntityUid Carried = default!;
- }
-}
diff --git a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs b/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs
deleted file mode 100644
index 0faff7d8078..00000000000
--- a/Content.Server/Nyanotrasen/Carrying/CarryingSystem.cs
+++ /dev/null
@@ -1,432 +0,0 @@
-using System.Numerics;
-using System.Threading;
-using Content.Server.DoAfter;
-using Content.Server.Body.Systems;
-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;
-using Content.Shared.Buckle.Components;
-using Content.Shared.Hands.Components;
-using Content.Shared.Hands;
-using Content.Shared.Stunnable;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Verbs;
-using Content.Shared.Climbing.Events; // Added this.
-using Content.Shared.Carrying;
-using Content.Shared.Movement.Events;
-using Content.Shared.Movement.Systems;
-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;
-
-namespace Content.Server.Carrying
-{
- public sealed class CarryingSystem : EntitySystem
- {
- [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!;
- [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly StandingStateSystem _standingState = default!;
- [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
- [Dependency] private readonly PullingSystem _pullingSystem = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- [Dependency] private readonly EscapeInventorySystem _escapeInventorySystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
- [Dependency] private readonly RespiratorSystem _respirator = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly PseudoItemSystem _pseudoItem = default!; // Needed for fitting check
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent>(AddCarryVerb);
- SubscribeLocalEvent>(AddInsertCarriedVerb);
- SubscribeLocalEvent(OnVirtualItemDeleted);
- SubscribeLocalEvent(OnThrow);
- SubscribeLocalEvent(OnParentChanged);
- SubscribeLocalEvent(OnMobStateChanged);
- SubscribeLocalEvent(OnInteractionAttempt);
- SubscribeLocalEvent(OnMoveInput);
- SubscribeLocalEvent(OnMoveAttempt);
- SubscribeLocalEvent(OnStandAttempt);
- SubscribeLocalEvent(OnInteractedWith);
- SubscribeLocalEvent(OnPullAttempt);
- SubscribeLocalEvent(OnStartClimb);
- SubscribeLocalEvent(OnBuckleChange);
- SubscribeLocalEvent(OnBuckleChange);
- SubscribeLocalEvent(OnBuckleChange);
- SubscribeLocalEvent(OnBuckleChange);
- SubscribeLocalEvent(OnDoAfter);
- }
-
- private void AddCarryVerb(EntityUid uid, CarriableComponent component, GetVerbsEvent args)
- {
- if (!args.CanInteract || !args.CanAccess)
- return;
-
- if (!CanCarry(args.User, uid, component))
- return;
-
- if (HasComp(args.User)) // yeah not dealing with that
- return;
-
- if (HasComp(args.User) || HasComp(args.Target))
- return;
-
- if (!_mobStateSystem.IsAlive(args.User))
- return;
-
- if (args.User == args.Target)
- return;
-
- AlternativeVerb verb = new()
- {
- Act = () =>
- {
- StartCarryDoAfter(args.User, uid, component);
- },
- Text = Loc.GetString("carry-verb"),
- Priority = 2
- };
- 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.
- ///
- private void OnVirtualItemDeleted(EntityUid uid, CarryingComponent component, VirtualItemDeletedEvent args)
- {
- if (!HasComp(args.BlockingEntity))
- return;
-
- DropCarried(uid, args.BlockingEntity);
- }
-
- ///
- /// Basically using virtual item passthrough to throw the carried person. A new age!
- /// Maybe other things besides throwing should use virt items like this...
- ///
- private void OnThrow(EntityUid uid, CarryingComponent component, BeforeThrowEvent args)
- {
- if (!TryComp(args.ItemUid, out var virtItem) || !HasComp(virtItem.BlockingEntity))
- return;
-
- args.ItemUid = virtItem.BlockingEntity;
-
- var multiplier = MassContest(uid, virtItem.BlockingEntity);
- args.ThrowSpeed = 5f * multiplier;
- }
-
- private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args)
- {
- var xform = Transform(uid);
- if (xform.MapUid != 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);
- }
-
- private void OnMobStateChanged(EntityUid uid, CarryingComponent component, MobStateChangedEvent args)
- {
- DropCarried(uid, component.Carried);
- }
-
- ///
- /// Only let the person being carried interact with their carrier and things on their person.
- ///
- private void OnInteractionAttempt(EntityUid uid, BeingCarriedComponent component, InteractionAttemptEvent args)
- {
- if (args.Target == null)
- return;
-
- var targetParent = Transform(args.Target.Value).ParentUid;
-
- if (args.Target.Value != component.Carrier && targetParent != component.Carrier && targetParent != uid)
- args.Cancelled = true;
- }
-
- ///
- /// Try to escape via the escape inventory system.
- ///
- private void OnMoveInput(EntityUid uid, BeingCarriedComponent component, ref MoveInputEvent args)
- {
- if (!TryComp(uid, out var escape))
- return;
-
- if (!args.HasDirectionalMovement)
- return;
-
- if (_actionBlockerSystem.CanInteract(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));
- }
- }
-
- private void OnMoveAttempt(EntityUid uid, BeingCarriedComponent component, UpdateCanMoveEvent args)
- {
- args.Cancel();
- }
-
- private void OnStandAttempt(EntityUid uid, BeingCarriedComponent component, StandAttemptEvent args)
- {
- args.Cancel();
- }
-
- private void OnInteractedWith(EntityUid uid, BeingCarriedComponent component, GettingInteractedWithAttemptEvent args)
- {
- if (args.Uid != component.Carrier)
- args.Cancelled = true;
- }
-
- private void OnPullAttempt(EntityUid uid, BeingCarriedComponent component, PullAttemptEvent args)
- {
- args.Cancelled = true;
- }
-
- private void OnStartClimb(EntityUid uid, BeingCarriedComponent component, ref StartClimbEvent args)
- {
- DropCarried(component.Carrier, uid);
- }
-
- private void OnBuckleChange(EntityUid uid, BeingCarriedComponent component, TEvent args) // Augh
- {
- DropCarried(component.Carrier, uid);
- }
-
- private void OnDoAfter(EntityUid uid, CarriableComponent component, CarryDoAfterEvent args)
- {
- component.CancelToken = null;
- if (args.Handled || args.Cancelled)
- return;
-
- if (!CanCarry(args.Args.User, uid, component))
- return;
-
- Carry(args.Args.User, uid);
- args.Handled = true;
- }
- private void StartCarryDoAfter(EntityUid carrier, EntityUid carried, CarriableComponent component)
- {
- TimeSpan length = GetPickupDuration(carrier, carried);
-
- if (length >= TimeSpan.FromSeconds(9))
- {
- _popupSystem.PopupEntity(Loc.GetString("carry-too-heavy"), carried, carrier, Shared.Popups.PopupType.SmallCaution);
- return;
- }
-
- if (!HasComp(carried))
- length *= 2f;
-
- component.CancelToken = new CancellationTokenSource();
-
- var ev = new CarryDoAfterEvent();
- var args = new DoAfterArgs(EntityManager, carrier, length, ev, carried, target: carried)
- {
- BreakOnMove = true,
- NeedHand = true
- };
-
- _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)
- {
- if (TryComp(carried, out var pullable))
- _pullingSystem.TryStopPull(carried, pullable);
-
- var carrierXform = Transform(carrier);
- var xform = Transform(carried);
- _transform.AttachToGridOrMap(carrier, carrierXform);
- _transform.AttachToGridOrMap(carried, xform);
- xform.Coordinates = carrierXform.Coordinates;
- _transform.SetParent(carried, xform, carrier, carrierXform);
-
- _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier);
- _virtualItemSystem.TrySpawnVirtualItemInHand(carried, carrier);
- var carryingComp = EnsureComp(carrier);
- ApplyCarrySlowdown(carrier, carried);
- var carriedComp = EnsureComp(carried);
- EnsureComp(carried);
-
- carryingComp.Carried = carried;
- carriedComp.Carrier = carrier;
-
- _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
- RemComp(carrier);
- RemComp(carried);
- RemComp(carried);
- _actionBlockerSystem.UpdateCanMove(carried);
- _virtualItemSystem.DeleteInHandsMatching(carrier, carried);
- Transform(carried).AttachToGridOrMap();
- _standingState.Stand(carried);
- _movementSpeed.RefreshMovementSpeedModifiers(carrier);
- }
-
- private void ApplyCarrySlowdown(EntityUid carrier, EntityUid carried)
- {
- var massRatio = MassContest(carrier, carried);
-
- if (massRatio == 0)
- massRatio = 1;
-
- var massRatioSq = Math.Pow(massRatio, 2);
- var modifier = (1 - (0.15 / massRatioSq));
- modifier = Math.Max(0.1, modifier);
- var slowdownComp = EnsureComp(carrier);
- _slowdown.SetModifier(carrier, (float) modifier, (float) modifier, slowdownComp);
- }
-
- public bool CanCarry(EntityUid carrier, EntityUid carried, CarriableComponent? carriedComp = null)
- {
- if (!Resolve(carried, ref carriedComp, false))
- return false;
-
- if (carriedComp.CancelToken != null)
- return false;
-
- if (!HasComp(Transform(carrier).ParentUid))
- return false;
-
- if (HasComp(carrier) || HasComp(carried))
- return false;
-
- // if (_respirator.IsReceivingCPR(carried))
- // return false;
-
- if (!TryComp(carrier, out var hands))
- return false;
-
- if (hands.CountFreeHands() < carriedComp.FreeHandsRequired)
- return false;
-
- return true;
- }
-
- private float MassContest(EntityUid roller, EntityUid target, PhysicsComponent? rollerPhysics = null, PhysicsComponent? targetPhysics = null)
- {
- if (!Resolve(roller, ref rollerPhysics, false) || !Resolve(target, ref targetPhysics, false))
- return 1f;
-
- if (targetPhysics.FixturesMass == 0)
- return 1f;
-
- 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 6df387e6ba8..7437d293da6 100644
--- a/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs
+++ b/Content.Server/Nyanotrasen/Item/PseudoItem/PseudoItemSystem.cs
@@ -1,9 +1,9 @@
-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.DeltaV.Carrying;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Item;
diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs
index 93a83465861..eec8ebb5072 100644
--- a/Content.Server/Resist/EscapeInventorySystem.cs
+++ b/Content.Server/Resist/EscapeInventorySystem.cs
@@ -1,6 +1,5 @@
using Content.Server.Popups;
using Content.Shared.Storage;
-using Content.Server.Carrying; // Carrying system from Nyanotrasen.
using Content.Shared.Inventory;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Storage.Components;
@@ -25,7 +24,6 @@ public sealed class EscapeInventorySystem : EntitySystem
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[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
///
@@ -109,13 +107,6 @@ private void OnEscape(EntityUid uid, CanEscapeInventoryComponent component, Esca
if (args.Handled || args.Cancelled)
return;
- if (TryComp(uid, out var carried)) // Start of carrying system of nyanotrasen.
- {
- _carryingSystem.DropCarried(carried.Carrier, uid);
- return;
- } // End of carrying system of nyanotrasen.
-
-
_containerSystem.AttachParentToContainerOrGrid((uid, Transform(uid)));
args.Handled = true;
}
diff --git a/Content.Shared/DeltaV/Carrying/BeingCarriedComponent.cs b/Content.Shared/DeltaV/Carrying/BeingCarriedComponent.cs
new file mode 100644
index 00000000000..7e519e7e04b
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/BeingCarriedComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+///
+/// Stores the carrier of an entity being carried.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSystem))]
+[AutoGenerateComponentState]
+public sealed partial class BeingCarriedComponent : Component
+{
+ [DataField, AutoNetworkedField]
+ public EntityUid Carrier;
+}
diff --git a/Content.Shared/DeltaV/Carrying/CarriableComponent.cs b/Content.Shared/DeltaV/Carrying/CarriableComponent.cs
new file mode 100644
index 00000000000..ad1968aec62
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/CarriableComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSystem))]
+public sealed partial class CarriableComponent : Component
+{
+ ///
+ /// Number of free hands required
+ /// to carry the entity
+ ///
+ [DataField]
+ public int FreeHandsRequired = 2;
+}
diff --git a/Content.Shared/DeltaV/Carrying/CarryDoAfterEvent.cs b/Content.Shared/DeltaV/Carrying/CarryDoAfterEvent.cs
new file mode 100644
index 00000000000..7ea0375518a
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/CarryDoAfterEvent.cs
@@ -0,0 +1,7 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+[Serializable, NetSerializable]
+public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent;
diff --git a/Content.Shared/DeltaV/Carrying/CarryingComponent.cs b/Content.Shared/DeltaV/Carrying/CarryingComponent.cs
new file mode 100644
index 00000000000..e6661da0e04
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/CarryingComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+///
+/// Added to an entity when they are carrying somebody.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSystem))]
+[AutoGenerateComponentState]
+public sealed partial class CarryingComponent : Component
+{
+ [DataField, AutoNetworkedField]
+ public EntityUid Carried;
+}
diff --git a/Content.Shared/DeltaV/Carrying/CarryingSlowdownComponent.cs b/Content.Shared/DeltaV/Carrying/CarryingSlowdownComponent.cs
new file mode 100644
index 00000000000..9e1be89370c
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/CarryingSlowdownComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(CarryingSlowdownSystem))]
+[AutoGenerateComponentState]
+public sealed partial class CarryingSlowdownComponent : Component
+{
+ ///
+ /// Modifier for both walk and sprint speed.
+ ///
+ [DataField, AutoNetworkedField]
+ public float Modifier = 1.0f;
+}
diff --git a/Content.Shared/DeltaV/Carrying/CarryingSlowdownSystem.cs b/Content.Shared/DeltaV/Carrying/CarryingSlowdownSystem.cs
new file mode 100644
index 00000000000..677c53eedc0
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/CarryingSlowdownSystem.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Movement.Systems;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+public sealed class CarryingSlowdownSystem : EntitySystem
+{
+ [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnRefreshMoveSpeed);
+ }
+
+ public void SetModifier(Entity ent, float modifier)
+ {
+ ent.Comp ??= EnsureComp(ent);
+ ent.Comp.Modifier = modifier;
+ Dirty(ent, ent.Comp);
+
+ _movementSpeed.RefreshMovementSpeedModifiers(ent);
+ }
+
+ private void OnRefreshMoveSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args)
+ {
+ args.ModifySpeed(ent.Comp.Modifier, ent.Comp.Modifier);
+ }
+}
diff --git a/Content.Shared/DeltaV/Carrying/CarryingSystem.cs b/Content.Shared/DeltaV/Carrying/CarryingSystem.cs
new file mode 100644
index 00000000000..2b47c49abd1
--- /dev/null
+++ b/Content.Shared/DeltaV/Carrying/CarryingSystem.cs
@@ -0,0 +1,384 @@
+using Content.Shared.ActionBlocker;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Climbing.Events;
+using Content.Shared.DoAfter;
+using Content.Shared.Hands;
+using Content.Shared.Hands.Components;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Inventory.VirtualItem;
+using Content.Shared.Item;
+using Content.Shared.Mobs;
+using Content.Shared.Movement.Events;
+using Content.Shared.Movement.Pulling.Components;
+using Content.Shared.Movement.Pulling.Events;
+using Content.Shared.Movement.Pulling.Systems;
+using Content.Shared.Movement.Systems;
+using Content.Shared.Nyanotrasen.Item.PseudoItem;
+using Content.Shared.Popups;
+using Content.Shared.Pulling;
+using Content.Shared.Resist;
+using Content.Shared.Standing;
+using Content.Shared.Storage;
+using Content.Shared.Stunnable;
+using Content.Shared.Throwing;
+using Content.Shared.Verbs;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Components;
+using System.Numerics;
+
+namespace Content.Shared.DeltaV.Carrying;
+
+public sealed class CarryingSystem : EntitySystem
+{
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly CarryingSlowdownSystem _slowdown = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
+ [Dependency] private readonly PullingSystem _pulling = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedPseudoItemSystem _pseudoItem = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly StandingStateSystem _standingState = default!;
+ [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!;
+
+ private EntityQuery _physicsQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _physicsQuery = GetEntityQuery();
+
+ SubscribeLocalEvent>(AddCarryVerb);
+ SubscribeLocalEvent>(AddInsertCarriedVerb);
+ SubscribeLocalEvent(OnVirtualItemDeleted);
+ SubscribeLocalEvent(OnThrow);
+ SubscribeLocalEvent(OnParentChanged);
+ SubscribeLocalEvent(OnMobStateChanged);
+ SubscribeLocalEvent(OnInteractionAttempt);
+ SubscribeLocalEvent(OnMoveAttempt);
+ SubscribeLocalEvent(OnStandAttempt);
+ SubscribeLocalEvent(OnInteractedWith);
+ SubscribeLocalEvent(OnPullAttempt);
+ SubscribeLocalEvent(OnDrop);
+ SubscribeLocalEvent(OnDrop);
+ SubscribeLocalEvent(OnDrop);
+ SubscribeLocalEvent(OnDrop);
+ SubscribeLocalEvent(OnDrop);
+ SubscribeLocalEvent(OnDrop);
+ SubscribeLocalEvent(OnDoAfter);
+ }
+
+ private void AddCarryVerb(Entity ent, ref GetVerbsEvent args)
+ {
+ var user = args.User;
+ var target = args.Target;
+ if (!args.CanInteract || !args.CanAccess || user == target)
+ return;
+
+ if (!CanCarry(user, ent))
+ return;
+
+ args.Verbs.Add(new AlternativeVerb()
+ {
+ Act = () => StartCarryDoAfter(user, ent),
+ Text = Loc.GetString("carry-verb"),
+ Priority = 2
+ });
+ }
+
+ private void AddInsertCarriedVerb(Entity ent, ref 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
+ // AKA put carried felenid into a duffelbag
+ if (args.Using is not {} carried || !args.CanAccess || !TryComp(carried, out var pseudoItem))
+ return;
+
+ var target = args.Target;
+ if (!TryComp(target, out var storageComp))
+ return;
+
+ if (!_pseudoItem.CheckItemFits((carried, pseudoItem), (target, storageComp)))
+ return;
+
+ args.Verbs.Add(new InnateVerb()
+ {
+ Act = () =>
+ {
+ DropCarried(ent, carried);
+ _pseudoItem.TryInsert(target, carried, pseudoItem, storageComp);
+ },
+ Text = Loc.GetString("action-name-insert-other", ("target", carried)),
+ Priority = 2
+ });
+ }
+
+ ///
+ /// Since the carried entity is stored as 2 virtual items, when deleted we want to drop them.
+ ///
+ private void OnVirtualItemDeleted(Entity ent, ref VirtualItemDeletedEvent args)
+ {
+ if (HasComp(args.BlockingEntity))
+ DropCarried(ent, args.BlockingEntity);
+ }
+
+ ///
+ /// Basically using virtual item passthrough to throw the carried person. A new age!
+ /// Maybe other things besides throwing should use virt items like this...
+ ///
+ private void OnThrow(Entity ent, ref BeforeThrowEvent args)
+ {
+ if (!TryComp(args.ItemUid, out var virtItem) || !HasComp(virtItem.BlockingEntity))
+ return;
+
+ var carried = virtItem.BlockingEntity;
+ args.ItemUid = carried;
+
+ args.ThrowSpeed = 5f * MassContest(ent, carried);
+ }
+
+ private void OnParentChanged(Entity ent, ref EntParentChangedMessage args)
+ {
+ var xform = Transform(ent);
+ if (xform.MapUid != args.OldMapId)
+ return;
+
+ // Do not drop the carried entity if the new parent is a grid
+ if (xform.ParentUid == xform.GridUid)
+ return;
+
+ DropCarried(ent, ent.Comp.Carried);
+ }
+
+ private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args)
+ {
+ DropCarried(ent, ent.Comp.Carried);
+ }
+
+ ///
+ /// Only let the person being carried interact with their carrier and things on their person.
+ ///
+ private void OnInteractionAttempt(Entity ent, ref InteractionAttemptEvent args)
+ {
+ if (args.Target is not {} target)
+ return;
+
+ var targetParent = Transform(target).ParentUid;
+
+ var carrier = ent.Comp.Carrier;
+ if (target != carrier && targetParent != carrier && targetParent != ent.Owner)
+ args.Cancelled = true;
+ }
+
+ private void OnMoveAttempt(Entity ent, ref UpdateCanMoveEvent args)
+ {
+ args.Cancel();
+ }
+
+ private void OnStandAttempt(Entity ent, ref StandAttemptEvent args)
+ {
+ args.Cancel();
+ }
+
+ private void OnInteractedWith(Entity ent, ref GettingInteractedWithAttemptEvent args)
+ {
+ if (args.Uid != ent.Comp.Carrier)
+ args.Cancelled = true;
+ }
+
+ private void OnPullAttempt(Entity ent, ref PullAttemptEvent args)
+ {
+ args.Cancelled = true;
+ }
+
+ private void OnDrop(Entity ent, ref TEvent args) // Augh
+ {
+ DropCarried(ent.Comp.Carrier, ent);
+ }
+
+ private void OnDoAfter(Entity ent, ref CarryDoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled)
+ return;
+
+ if (!CanCarry(args.Args.User, ent))
+ return;
+
+ Carry(args.Args.User, ent);
+ args.Handled = true;
+ }
+
+ private void StartCarryDoAfter(EntityUid carrier, Entity carried)
+ {
+ TimeSpan length = GetPickupDuration(carrier, carried);
+
+ if (length.TotalSeconds >= 9f)
+ {
+ _popup.PopupClient(Loc.GetString("carry-too-heavy"), carried, carrier, PopupType.SmallCaution);
+ return;
+ }
+
+ if (!HasComp(carried))
+ length *= 2f;
+
+ var ev = new CarryDoAfterEvent();
+ var args = new DoAfterArgs(EntityManager, carrier, length, ev, carried, target: carried)
+ {
+ BreakOnMove = true,
+ NeedHand = true
+ };
+
+ _doAfter.TryStartDoAfter(args);
+
+ // Show a popup to the person getting picked up
+ _popup.PopupEntity(Loc.GetString("carry-started", ("carrier", carrier)), carried, carried);
+ }
+
+ private void Carry(EntityUid carrier, EntityUid carried)
+ {
+ if (TryComp(carried, out var pullable))
+ _pulling.TryStopPull(carried, pullable);
+
+ var carrierXform = Transform(carrier);
+ var xform = Transform(carried);
+ _transform.AttachToGridOrMap(carrier, carrierXform);
+ _transform.AttachToGridOrMap(carried, xform);
+ _transform.SetParent(carried, xform, carrier, carrierXform);
+
+ var carryingComp = EnsureComp(carrier);
+ carryingComp.Carried = carried;
+ Dirty(carrier, carryingComp);
+ var carriedComp = EnsureComp(carried);
+ carriedComp.Carrier = carrier;
+ Dirty(carried, carriedComp);
+ EnsureComp(carried);
+
+ ApplyCarrySlowdown(carrier, carried);
+
+ _actionBlocker.UpdateCanMove(carried);
+
+ if (_net.IsClient) // no spawning prediction
+ return;
+
+ _virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
+ _virtualItem.TrySpawnVirtualItemInHand(carried, carrier);
+ }
+
+ public bool TryCarry(EntityUid carrier, Entity toCarry)
+ {
+ if (!Resolve(toCarry, ref toCarry.Comp, false))
+ return false;
+
+ if (!CanCarry(carrier, (toCarry, toCarry.Comp)))
+ 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).TotalSeconds > 9f)
+ return false;
+
+ Carry(carrier, toCarry);
+ return true;
+ }
+
+ public void DropCarried(EntityUid carrier, EntityUid carried)
+ {
+ Drop(carried);
+ RemComp(carrier); // get rid of this first so we don't recursively fire that event
+ RemComp(carrier);
+ _virtualItem.DeleteInHandsMatching(carrier, carried);
+ _movementSpeed.RefreshMovementSpeedModifiers(carrier);
+ }
+
+ private void Drop(EntityUid carried)
+ {
+ RemComp(carried);
+ RemComp(carried); // TODO SHITMED: make sure this doesnt let you make someone with no legs walk
+ _actionBlocker.UpdateCanMove(carried);
+ Transform(carried).AttachToGridOrMap();
+ _standingState.Stand(carried);
+ }
+
+ private void ApplyCarrySlowdown(EntityUid carrier, EntityUid carried)
+ {
+ var massRatio = MassContest(carrier, carried);
+
+ if (massRatio == 0)
+ massRatio = 1;
+
+ var massRatioSq = Math.Pow(massRatio, 2);
+ var modifier = (1 - (0.15 / massRatioSq));
+ modifier = Math.Max(0.1, modifier);
+ _slowdown.SetModifier(carrier, (float) modifier);
+ }
+
+ public bool CanCarry(EntityUid carrier, Entity carried)
+ {
+ return
+ carrier != carried.Owner &&
+ // can't carry multiple people, even if you have 4 hands it will break invariants when removing carryingcomponent for first carried person
+ !HasComp(carrier) &&
+ // can't carry someone in a locker, buckled, etc
+ HasComp(Transform(carrier).ParentUid) &&
+ // no tower of spacemen or stack overflow
+ !HasComp(carrier) &&
+ !HasComp(carried) &&
+ // finally check that there are enough free hands
+ TryComp(carrier, out var hands) &&
+ hands.CountFreeHands() >= carried.Comp.FreeHandsRequired;
+ }
+
+ private float MassContest(EntityUid roller, EntityUid target)
+ {
+ if (!_physicsQuery.TryComp(roller, out var rollerPhysics) || !_physicsQuery.TryComp(target, out var targetPhysics))
+ return 1f;
+
+ if (targetPhysics.FixturesMass == 0)
+ return 1f;
+
+ 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)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var carried, out var comp, out var xform))
+ {
+ var carrier = comp.Carrier;
+ if (TerminatingOrDeleted(carrier))
+ {
+ RemCompDeferred(carried);
+ 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 (xform.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
+ _transform.SetLocalPosition(carried, Vector2.Zero);
+ }
+ }
+}
diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs
deleted file mode 100644
index 6acd6b775f3..00000000000
--- a/Content.Shared/Nyanotrasen/Carrying/CarryingDoAfterEvent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Robust.Shared.Serialization;
-using Content.Shared.DoAfter;
-
-namespace Content.Shared.Carrying
-{
- [Serializable, NetSerializable]
- public sealed partial class CarryDoAfterEvent : SimpleDoAfterEvent
- {
- }
-}
diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs
deleted file mode 100644
index aabde66af0d..00000000000
--- a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownComponent.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Carrying
-{
- [RegisterComponent, NetworkedComponent, Access(typeof(CarryingSlowdownSystem))]
-
- public sealed partial class CarryingSlowdownComponent : Component
- {
- [DataField("walkModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
- public float WalkModifier = 1.0f;
-
- [DataField("sprintModifier", required: true)] [ViewVariables(VVAccess.ReadWrite)]
- public float SprintModifier = 1.0f;
- }
-
- [Serializable, NetSerializable]
- public sealed class CarryingSlowdownComponentState : ComponentState
- {
- public float WalkModifier;
- public float SprintModifier;
- public CarryingSlowdownComponentState(float walkModifier, float sprintModifier)
- {
- WalkModifier = walkModifier;
- SprintModifier = sprintModifier;
- }
- }
-}
diff --git a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs b/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs
deleted file mode 100644
index 9b9c8cec10f..00000000000
--- a/Content.Shared/Nyanotrasen/Carrying/CarryingSlowdownSystem.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using Content.Shared.Movement.Systems;
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Carrying
-{
- public sealed class CarryingSlowdownSystem : EntitySystem
- {
- [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnGetState);
- SubscribeLocalEvent(OnHandleState);
- SubscribeLocalEvent(OnRefreshMoveSpeed);
- }
-
- public void SetModifier(EntityUid uid, float walkSpeedModifier, float sprintSpeedModifier, CarryingSlowdownComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
-
- component.WalkModifier = walkSpeedModifier;
- component.SprintModifier = sprintSpeedModifier;
- _movementSpeed.RefreshMovementSpeedModifiers(uid);
- }
- private void OnGetState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentGetState args)
- {
- args.State = new CarryingSlowdownComponentState(component.WalkModifier, component.SprintModifier);
- }
-
- private void OnHandleState(EntityUid uid, CarryingSlowdownComponent component, ref ComponentHandleState args)
- {
- if (args.Current is CarryingSlowdownComponentState state)
- {
- component.WalkModifier = state.WalkModifier;
- component.SprintModifier = state.SprintModifier;
-
- _movementSpeed.RefreshMovementSpeedModifiers(uid);
- }
- }
- private void OnRefreshMoveSpeed(EntityUid uid, CarryingSlowdownComponent component, RefreshMovementSpeedModifiersEvent args)
- {
- args.ModifySpeed(component.WalkModifier, component.SprintModifier);
- }
- }
-}