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