diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
index 9a09feae2a..c13baa28ef 100644
--- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
+++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs
@@ -48,10 +48,16 @@ public sealed partial class PullerComponent : Component
public bool NeedsHands = true;
///
- /// The specific force of pushing, in newtons per kilogram. This is multiplied by the puller's physics mass.
+ /// The maximum acceleration of pushing, in meters per second squared.
///
[DataField]
- public float SpecificForce = 0.3f;
+ public float PushAcceleration = 0.3f;
+
+ ///
+ /// The maximum speed to which the pulled entity may be accelerated relative to the push direction, in meters per second.
+ ///
+ [DataField]
+ public float MaxPushSpeed = 1.2f;
///
/// The maximum distance between the puller and the point towards which the puller may attempt to pull it, in meters.
diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
index 2dc54ddbc9..e6acabc33c 100644
--- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
+++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs
@@ -112,7 +112,7 @@ public override void Update(float frameTime)
if (pullerComp.PushingTowards is null)
continue;
- // If pushing but the target position is invalid, or the push action has expired, stop pushing
+ // If pushing but the target position is invalid, or the push action has expired or finished, stop pushing
if (pullerComp.NextPushStop < _timing.CurTime
|| !(pullerComp.PushingTowards.Value.ToMap(EntityManager, _xformSys) is var pushCoordinates)
|| pushCoordinates.MapId != pulledXForm.MapID)
@@ -122,20 +122,28 @@ public override void Update(float frameTime)
continue;
}
+ // Actual force calculation. All the Vector2's below are in map coordinates.
var desiredDeltaPos = pushCoordinates.Position - Transform(pulled).Coordinates.ToMapPos(EntityManager, _xformSys);
- var desiredForce = pulledPhysics.Mass * desiredDeltaPos;
- var maxSourceForce = pullerComp.SpecificForce * pullerPhysics.Mass;
- var actualForce = desiredForce.LengthSquared() > maxSourceForce * maxSourceForce ? desiredDeltaPos.Normalized() * maxSourceForce : desiredForce;
+ if (desiredDeltaPos.LengthSquared() < 0.1f)
+ {
+ pullerComp.PushingTowards = null;
+ continue;
+ }
- // Cannot use ApplyForce here because it will be cleared on the next physics substep which will render it ultimately useless
+ var velocityAndDirectionAngle = new Angle(pulledPhysics.LinearVelocity) - new Angle(desiredDeltaPos);
+ var currentRelativeSpeed = pulledPhysics.LinearVelocity.Length() * (float) Math.Cos(velocityAndDirectionAngle.Theta);
+ var desiredAcceleration = MathF.Max(0f, pullerComp.MaxPushSpeed - currentRelativeSpeed);
+
+ var desiredImpulse = pulledPhysics.Mass * desiredDeltaPos;
+ var maxSourceImpulse = MathF.Min(pullerComp.PushAcceleration, desiredAcceleration) * pullerPhysics.Mass;
+ var actualImpulse = desiredImpulse.LengthSquared() > maxSourceImpulse * maxSourceImpulse ? desiredDeltaPos.Normalized() * maxSourceImpulse : desiredImpulse;
+
+ // Ideally we'd want to apply forces instead of impulses, however...
+ // We cannot use ApplyForce here because it will be cleared on the next physics substep which will render it ultimately useless
// The alternative is to run this function on every physics substep, but that is way too expensive for such a minor system
- _physics.ApplyLinearImpulse(pulled, actualForce);
- _physics.ApplyLinearImpulse(puller, -actualForce);
+ _physics.ApplyLinearImpulse(pulled, actualImpulse);
+ _physics.ApplyLinearImpulse(puller, -actualImpulse);
pulledComp.BeingActivelyPushed = true;
-
- // Means the pullee has been pushed close enough to the destination
- if (actualForce == desiredForce)
- pullerComp.PushingTowards = null;
}
query.Dispose();
}
diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
index 33e51cc4c9..e0300fa503 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
@@ -25,7 +25,7 @@
- type: GhostHearing
- type: Hands
- type: Puller
- specificForce: 1000
+ pushAcceleration: 1000000 # Will still be capped in max speed
maxPushRange: 20
- type: CombatMode
- type: Physics