diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs
index c6bb5fa6cc..5c8968dd98 100644
--- a/Content.Server/Physics/Controllers/MoverController.cs
+++ b/Content.Server/Physics/Controllers/MoverController.cs
@@ -232,6 +232,29 @@ namespace Content.Server.Physics.Controllers
component.CurTickBraking += brake * fraction;
}
+ ///
+ /// Helper function to extrapolate max velocity for a given Vector2 (really, its angle) and shuttle.
+ ///
+ private Vector2 ObtainMaxVel(Vector2 vel, ShuttleComponent shuttle)
+ {
+ if (vel.Length() == 0f)
+ return Vector2.Zero;
+
+ // this math could PROBABLY be simplified for performance
+ // probably
+ // __________________________________
+ // / / __ __ \2 / __ __ \2
+ // O = I : _ / |I * | 1/H | | + |I * | 0 | |
+ // V \ |_ 0 _| / \ |_1/V_| /
+
+ var horizIndex = vel.X > 0 ? 1 : 3; // east else west
+ var vertIndex = vel.Y > 0 ? 2 : 0; // north else south
+ var horizComp = MathF.Pow(Vector2.Dot(vel, new (shuttle.BaseLinearThrust[horizIndex] / shuttle.LinearThrust[horizIndex], 0f)), 2);
+ var vertComp = MathF.Pow(Vector2.Dot(vel, new (0f, shuttle.BaseLinearThrust[vertIndex] / shuttle.LinearThrust[vertIndex])), 2);
+
+ return shuttle.BaseMaxLinearVelocity * vel * MathF.ReciprocalSqrtEstimate(horizComp + vertComp);
+ }
+
private void HandleShuttleMovement(float frameTime)
{
var newPilots = new Dictionary)>();
@@ -478,19 +501,27 @@ namespace Content.Server.Physics.Controllers
totalForce += impulse;
}
- totalForce = shuttleNorthAngle.RotateVec(totalForce);
-
var forceMul = frameTime * body.InvMass;
- var maxVelocity = (ShuttleComponent.MaxLinearVelocity - body.LinearVelocity.Length()) / forceMul;
- if (maxVelocity != 0f)
- {
- // Don't overshoot
- if (totalForce.Length() > maxVelocity)
- totalForce = totalForce.Normalized() * maxVelocity;
+ var localVel = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
+ var maxVelocity = ObtainMaxVel(localVel, shuttle); // max for current travel dir
+ var maxWishVelocity = ObtainMaxVel(totalForce, shuttle);
+ var properAccel = (maxWishVelocity - localVel) / forceMul;
- PhysicsSystem.ApplyForce(shuttleUid, totalForce, body: body);
- }
+ var finalForce = Vector2.Dot(totalForce, properAccel.Normalized()) * properAccel.Normalized();
+
+ if (localVel.Length() >= maxVelocity.Length() && Vector2.Dot(totalForce, localVel) > 0f)
+ finalForce = Vector2.Zero; // burn would be faster if used as such
+
+ if (finalForce.Length() > properAccel.Length())
+ finalForce = properAccel; // don't overshoot
+
+ //Logger.Info($"shuttle: maxVelocity {maxVelocity} totalForce {totalForce} finalForce {finalForce} forceMul {forceMul} properAccel {properAccel}");
+
+ finalForce = shuttleNorthAngle.RotateVec(finalForce);
+
+ if (finalForce.Length() > 0f)
+ PhysicsSystem.ApplyForce(shuttleUid, finalForce, body: body);
}
if (MathHelper.CloseTo(angularInput, 0f))
diff --git a/Content.Server/Shuttles/Components/ShuttleComponent.cs b/Content.Server/Shuttles/Components/ShuttleComponent.cs
index 25415636e0..488f1d3a21 100644
--- a/Content.Server/Shuttles/Components/ShuttleComponent.cs
+++ b/Content.Server/Shuttles/Components/ShuttleComponent.cs
@@ -16,7 +16,11 @@ namespace Content.Server.Shuttles.Components
///
public const float BrakeCoefficient = 1.5f;
- public const float MaxLinearVelocity = 20f;
+ ///
+ /// Maximum velocity assuming unupgraded, tier 1 thrusters
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float BaseMaxLinearVelocity = 20f;
public const float MaxAngularVelocity = 4f;
@@ -26,6 +30,12 @@ namespace Content.Server.Shuttles.Components
[ViewVariables]
public readonly float[] LinearThrust = new float[4];
+ ///
+ /// The cached thrust available for each cardinal direction, if all thrusters are T1
+ ///
+ [ViewVariables]
+ public readonly float[] BaseLinearThrust = new float[4];
+
///
/// The thrusters contributing to each direction for impulse.
///
diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs
index a9bc0f4e71..24d326c94e 100644
--- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs
+++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs
@@ -173,10 +173,12 @@ public sealed class ThrusterSystem : EntitySystem
var direction = (int) args.NewRotation.GetCardinalDir() / 2;
shuttleComponent.LinearThrust[oldDirection] -= component.Thrust;
+ shuttleComponent.BaseLinearThrust[oldDirection] -= component.BaseThrust;
DebugTools.Assert(shuttleComponent.LinearThrusters[oldDirection].Contains(uid));
shuttleComponent.LinearThrusters[oldDirection].Remove(uid);
shuttleComponent.LinearThrust[direction] += component.Thrust;
+ shuttleComponent.BaseLinearThrust[direction] += component.BaseThrust;
DebugTools.Assert(!shuttleComponent.LinearThrusters[direction].Contains(uid));
shuttleComponent.LinearThrusters[direction].Add(uid);
}
@@ -257,6 +259,7 @@ public sealed class ThrusterSystem : EntitySystem
var direction = (int) xform.LocalRotation.GetCardinalDir() / 2;
shuttleComponent.LinearThrust[direction] += component.Thrust;
+ shuttleComponent.BaseLinearThrust[direction] += component.BaseThrust;
DebugTools.Assert(!shuttleComponent.LinearThrusters[direction].Contains(uid));
shuttleComponent.LinearThrusters[direction].Add(uid);
@@ -355,6 +358,7 @@ public sealed class ThrusterSystem : EntitySystem
var direction = (int) angle.Value.GetCardinalDir() / 2;
shuttleComponent.LinearThrust[direction] -= component.Thrust;
+ shuttleComponent.BaseLinearThrust[direction] -= component.BaseThrust;
DebugTools.Assert(shuttleComponent.LinearThrusters[direction].Contains(uid));
shuttleComponent.LinearThrusters[direction].Remove(uid);
break;
@@ -552,9 +556,15 @@ public sealed class ThrusterSystem : EntitySystem
private void OnRefreshParts(EntityUid uid, ThrusterComponent component, RefreshPartsEvent args)
{
+ if (component.IsOn) // safely disable thruster to prevent negative thrust
+ DisableThruster(uid, component);
+
var thrustRating = args.PartRatings[component.MachinePartThrust];
component.Thrust = component.BaseThrust * MathF.Pow(component.PartRatingThrustMultiplier, thrustRating - 1);
+
+ if (component.Enabled && CanEnable(uid, component))
+ EnableThruster(uid, component);
}
private void OnUpgradeExamine(EntityUid uid, ThrusterComponent component, UpgradeExamineEvent args)