Make shuttle brakes use thrusters and bandaid shuttle movement (#9826)
This commit is contained in:
@@ -13,7 +13,6 @@ namespace Content.Server.Physics.Controllers
|
||||
public sealed class MoverController : SharedMoverController
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
[Dependency] private readonly ThrusterSystem _thruster = default!;
|
||||
|
||||
private Dictionary<ShuttleComponent, List<(PilotComponent, InputMoverComponent, TransformComponent)>> _shuttlePilots = new();
|
||||
@@ -233,49 +232,20 @@ namespace Content.Server.Physics.Controllers
|
||||
{
|
||||
if (Paused(shuttle.Owner) || FTLLocked(shuttle) || !TryComp(shuttle.Owner, out PhysicsComponent? body)) continue;
|
||||
|
||||
var shuttleNorthAngle = Transform(body.Owner).WorldRotation;
|
||||
|
||||
// Collate movement linear and angular inputs together
|
||||
var linearInput = Vector2.Zero;
|
||||
var brakeInput = 0f;
|
||||
var angularInput = 0f;
|
||||
|
||||
foreach (var (pilot, _, consoleXform) in pilots)
|
||||
{
|
||||
var pilotInput = GetPilotVelocityInput(pilot);
|
||||
|
||||
// On the one hand we could just make it relay inputs to brake
|
||||
// but uhh may be disorienting? n
|
||||
if (pilotInput.Brakes > 0f)
|
||||
{
|
||||
if (body.LinearVelocity.Length > 0f)
|
||||
{
|
||||
var force = body.LinearVelocity.Normalized * pilotInput.Brakes / body.InvMass * 3f;
|
||||
var impulse = force * body.InvMass * frameTime;
|
||||
|
||||
if (impulse.Length > body.LinearVelocity.Length)
|
||||
{
|
||||
body.LinearVelocity = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.ApplyLinearImpulse(-force * frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (body.AngularVelocity != 0f)
|
||||
{
|
||||
var force = body.AngularVelocity * pilotInput.Brakes / body.InvI * 2f;
|
||||
var impulse = force * body.InvI * frameTime;
|
||||
|
||||
if (MathF.Abs(impulse) > MathF.Abs(body.AngularVelocity))
|
||||
{
|
||||
body.AngularVelocity = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.ApplyAngularImpulse(-force * frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
brakeInput += pilotInput.Brakes;
|
||||
}
|
||||
|
||||
if (pilotInput.Strafe.Length > 0f)
|
||||
@@ -293,12 +263,141 @@ namespace Content.Server.Physics.Controllers
|
||||
var count = pilots.Count;
|
||||
linearInput /= count;
|
||||
angularInput /= count;
|
||||
brakeInput /= count;
|
||||
|
||||
/*
|
||||
* So essentially:
|
||||
* 1. We do the same calcs for braking as we do for linear thrust so it's similar to a player pressing it
|
||||
* but we also need to handle when they get close to 0 hence why it sets velocity directly.
|
||||
*
|
||||
* 2. We do a similar calculation to mob movement where the closer you are to your speed cap the slower you accelerate
|
||||
*
|
||||
* TODO: Could combine braking linear input and thrust more but my brain was just not working debugging
|
||||
* TODO: Need to have variable speed caps based on thruster count or whatever
|
||||
*/
|
||||
|
||||
// Handle shuttle movement
|
||||
if (linearInput.Length.Equals(0f))
|
||||
if (brakeInput > 0f)
|
||||
{
|
||||
if (body.LinearVelocity.Length > 0f)
|
||||
{
|
||||
// Get velocity relative to the shuttle so we know which thrusters to fire
|
||||
var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
|
||||
var force = Vector2.Zero;
|
||||
|
||||
if (shuttleVelocity.X < 0f)
|
||||
{
|
||||
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.West);
|
||||
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.East);
|
||||
|
||||
var index = (int) Math.Log2((int) DirectionFlag.East);
|
||||
force.X += shuttle.LinearThrust[index];
|
||||
}
|
||||
else if (shuttleVelocity.X > 0f)
|
||||
{
|
||||
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.East);
|
||||
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.West);
|
||||
|
||||
var index = (int) Math.Log2((int) DirectionFlag.West);
|
||||
force.X -= shuttle.LinearThrust[index];
|
||||
}
|
||||
|
||||
if (shuttleVelocity.Y < 0f)
|
||||
{
|
||||
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.South);
|
||||
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North);
|
||||
|
||||
var index = (int) Math.Log2((int) DirectionFlag.North);
|
||||
force.Y += shuttle.LinearThrust[index];
|
||||
}
|
||||
else if (shuttleVelocity.Y > 0f)
|
||||
{
|
||||
_thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.North);
|
||||
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
|
||||
|
||||
var index = (int) Math.Log2((int) DirectionFlag.South);
|
||||
force.Y -= shuttle.LinearThrust[index];
|
||||
}
|
||||
|
||||
var impulse = force * brakeInput;
|
||||
var wishDir = impulse.Normalized;
|
||||
// TODO: Adjust max possible speed based on total thrust in particular direction.
|
||||
var wishSpeed = 20f;
|
||||
|
||||
var currentSpeed = Vector2.Dot(shuttleVelocity, wishDir);
|
||||
var addSpeed = wishSpeed - currentSpeed;
|
||||
|
||||
if (addSpeed > 0f)
|
||||
{
|
||||
var accelSpeed = impulse.Length * frameTime;
|
||||
accelSpeed = MathF.Min(accelSpeed, addSpeed);
|
||||
impulse = impulse.Normalized * accelSpeed * body.InvMass;
|
||||
|
||||
// Cap inputs
|
||||
if (shuttleVelocity.X < 0f)
|
||||
{
|
||||
impulse.X = MathF.Min(impulse.X, -shuttleVelocity.X);
|
||||
}
|
||||
else if (shuttleVelocity.X > 0f)
|
||||
{
|
||||
impulse.X = MathF.Max(impulse.X, -shuttleVelocity.X);
|
||||
}
|
||||
|
||||
if (shuttleVelocity.Y < 0f)
|
||||
{
|
||||
impulse.Y = MathF.Min(impulse.Y, -shuttleVelocity.Y);
|
||||
}
|
||||
else if (shuttleVelocity.Y > 0f)
|
||||
{
|
||||
impulse.Y = MathF.Max(impulse.Y, -shuttleVelocity.Y);
|
||||
}
|
||||
|
||||
PhysicsSystem.SetLinearVelocity(body, body.LinearVelocity + shuttleNorthAngle.RotateVec(impulse));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_thruster.DisableLinearThrusters(shuttle);
|
||||
body.LinearDamping = _shuttle.ShuttleIdleLinearDamping * body.InvMass;
|
||||
}
|
||||
|
||||
if (body.AngularVelocity != 0f)
|
||||
{
|
||||
var impulse = shuttle.AngularThrust * brakeInput * (body.AngularVelocity > 0f ? -1f : 1f);
|
||||
var wishSpeed = MathF.PI;
|
||||
|
||||
if (impulse < 0f)
|
||||
wishSpeed *= -1f;
|
||||
|
||||
var currentSpeed = body.AngularVelocity;
|
||||
var addSpeed = wishSpeed - currentSpeed;
|
||||
|
||||
if (!addSpeed.Equals(0f))
|
||||
{
|
||||
var accelSpeed = impulse * body.InvI * frameTime;
|
||||
|
||||
if (accelSpeed < 0f)
|
||||
accelSpeed = MathF.Max(accelSpeed, addSpeed);
|
||||
else
|
||||
accelSpeed = MathF.Min(accelSpeed, addSpeed);
|
||||
|
||||
if (body.AngularVelocity < 0f && body.AngularVelocity + accelSpeed > 0f)
|
||||
accelSpeed = -body.AngularVelocity;
|
||||
else if (body.AngularVelocity > 0f && body.AngularVelocity + accelSpeed < 0f)
|
||||
accelSpeed = -body.AngularVelocity;
|
||||
|
||||
PhysicsSystem.SetAngularVelocity(body, body.AngularVelocity + accelSpeed);
|
||||
_thruster.SetAngularThrust(shuttle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (linearInput.Length.Equals(0f))
|
||||
{
|
||||
body.SleepingAllowed = true;
|
||||
|
||||
if (brakeInput.Equals(0f))
|
||||
_thruster.DisableLinearThrusters(shuttle);
|
||||
|
||||
if (body.LinearVelocity.Length < 0.08)
|
||||
{
|
||||
body.LinearVelocity = Vector2.Zero;
|
||||
@@ -306,11 +405,10 @@ namespace Content.Server.Physics.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
body.LinearDamping = 0;
|
||||
body.SleepingAllowed = false;
|
||||
var angle = linearInput.ToWorldAngle();
|
||||
var linearDir = angle.GetDir();
|
||||
var dockFlag = linearDir.AsFlag();
|
||||
var shuttleNorth = EntityManager.GetComponent<TransformComponent>(body.Owner).WorldRotation.ToWorldVec();
|
||||
|
||||
var totalForce = new Vector2();
|
||||
|
||||
@@ -335,46 +433,52 @@ namespace Content.Server.Physics.Controllers
|
||||
continue;
|
||||
}
|
||||
|
||||
float length;
|
||||
Angle thrustAngle;
|
||||
var index = (int) Math.Log2((int) dir);
|
||||
var thrust = shuttle.LinearThrust[index];
|
||||
|
||||
switch (dir)
|
||||
{
|
||||
case DirectionFlag.North:
|
||||
length = linearInput.Y;
|
||||
thrustAngle = new Angle(MathF.PI);
|
||||
totalForce.Y += thrust;
|
||||
break;
|
||||
case DirectionFlag.South:
|
||||
length = -linearInput.Y;
|
||||
thrustAngle = new Angle(0f);
|
||||
totalForce.Y -= thrust;
|
||||
break;
|
||||
case DirectionFlag.East:
|
||||
length = linearInput.X;
|
||||
thrustAngle = new Angle(MathF.PI / 2f);
|
||||
totalForce.X += thrust;
|
||||
break;
|
||||
case DirectionFlag.West:
|
||||
length = -linearInput.X;
|
||||
thrustAngle = new Angle(-MathF.PI / 2f);
|
||||
totalForce.X -= thrust;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_thruster.EnableLinearThrustDirection(shuttle, dir);
|
||||
|
||||
var index = (int) Math.Log2((int) dir);
|
||||
var force = thrustAngle.RotateVec(shuttleNorth) * shuttle.LinearThrust[index] * length;
|
||||
|
||||
totalForce += force;
|
||||
}
|
||||
|
||||
body.ApplyLinearImpulse(totalForce * frameTime);
|
||||
// We don't want to touch damping if no inputs are given
|
||||
// so we'll just add an artifical drag to the velocity input.
|
||||
var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity);
|
||||
|
||||
var wishDir = totalForce.Normalized;
|
||||
// TODO: Adjust max possible speed based on total thrust in particular direction.
|
||||
var wishSpeed = 20f;
|
||||
|
||||
var currentSpeed = Vector2.Dot(shuttleVelocity, wishDir);
|
||||
var addSpeed = wishSpeed - currentSpeed;
|
||||
|
||||
if (addSpeed > 0f)
|
||||
{
|
||||
var accelSpeed = totalForce.Length * frameTime;
|
||||
accelSpeed = MathF.Min(accelSpeed, addSpeed);
|
||||
body.ApplyLinearImpulse(shuttleNorthAngle.RotateVec(totalForce.Normalized * accelSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
if (MathHelper.CloseTo(angularInput, 0f))
|
||||
{
|
||||
_thruster.SetAngularThrust(shuttle, false);
|
||||
body.AngularDamping = _shuttle.ShuttleIdleAngularDamping * body.InvI;
|
||||
body.SleepingAllowed = true;
|
||||
|
||||
if (Math.Abs(body.AngularVelocity) < 0.01f)
|
||||
@@ -384,21 +488,31 @@ namespace Content.Server.Physics.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
body.AngularDamping = 0;
|
||||
body.SleepingAllowed = false;
|
||||
var impulse = shuttle.AngularThrust * -angularInput;
|
||||
var wishSpeed = MathF.PI;
|
||||
|
||||
var maxSpeed = Math.Min(_shuttle.ShuttleMaxAngularMomentum * body.InvI, _shuttle.ShuttleMaxAngularSpeed);
|
||||
var maxTorque = body.Inertia * _shuttle.ShuttleMaxAngularAcc;
|
||||
if (impulse < 0f)
|
||||
wishSpeed *= -1f;
|
||||
|
||||
var torque = Math.Min(shuttle.AngularThrust, maxTorque);
|
||||
var dragTorque = body.AngularVelocity * (torque / maxSpeed);
|
||||
var currentSpeed = body.AngularVelocity;
|
||||
var addSpeed = wishSpeed - currentSpeed;
|
||||
|
||||
body.ApplyAngularImpulse((-angularInput * torque - dragTorque) * frameTime);
|
||||
if (!addSpeed.Equals(0f))
|
||||
{
|
||||
var accelSpeed = impulse * body.InvI * frameTime;
|
||||
|
||||
if (accelSpeed < 0f)
|
||||
accelSpeed = MathF.Max(accelSpeed, addSpeed);
|
||||
else
|
||||
accelSpeed = MathF.Min(accelSpeed, addSpeed);
|
||||
|
||||
PhysicsSystem.SetAngularVelocity(body, body.AngularVelocity + accelSpeed);
|
||||
_thruster.SetAngularThrust(shuttle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool FTLLocked(ShuttleComponent shuttle)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Content.Server.Shuttles.Components
|
||||
/// <summary>
|
||||
/// The thrusters contributing to the angular impulse of the shuttle.
|
||||
/// </summary>
|
||||
public readonly List<ThrusterComponent> AngularThrusters = new List<ThrusterComponent>();
|
||||
public readonly List<ThrusterComponent> AngularThrusters = new();
|
||||
|
||||
[ViewVariables]
|
||||
public float AngularThrust = 0f;
|
||||
|
||||
@@ -261,8 +261,8 @@ public sealed partial class ShuttleSystem
|
||||
{
|
||||
body.LinearVelocity = Vector2.Zero;
|
||||
body.AngularVelocity = 0f;
|
||||
body.LinearDamping = ShuttleIdleLinearDamping;
|
||||
body.AngularDamping = ShuttleIdleAngularDamping;
|
||||
body.LinearDamping = ShuttleLinearDamping;
|
||||
body.AngularDamping = ShuttleAngularDamping;
|
||||
}
|
||||
|
||||
TryComp(comp.Owner, out shuttle);
|
||||
|
||||
@@ -21,14 +21,8 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
public const float TileMassMultiplier = 0.5f;
|
||||
|
||||
public float ShuttleMaxLinearSpeed;
|
||||
|
||||
public float ShuttleMaxAngularMomentum;
|
||||
public float ShuttleMaxAngularAcc;
|
||||
public float ShuttleMaxAngularSpeed;
|
||||
|
||||
public float ShuttleIdleLinearDamping;
|
||||
public float ShuttleIdleAngularDamping;
|
||||
public const float ShuttleLinearDamping = 0.05f;
|
||||
public const float ShuttleAngularDamping = 0.05f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -47,14 +41,6 @@ namespace Content.Server.Shuttles.Systems
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
|
||||
SubscribeLocalEvent<GridFixtureChangeEvent>(OnGridFixtureChange);
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CCVars.ShuttleMaxLinearSpeed, SetShuttleMaxLinearSpeed, true);
|
||||
configManager.OnValueChanged(CCVars.ShuttleMaxAngularSpeed, SetShuttleMaxAngularSpeed, true);
|
||||
configManager.OnValueChanged(CCVars.ShuttleIdleLinearDamping, SetShuttleIdleLinearDamping, true);
|
||||
configManager.OnValueChanged(CCVars.ShuttleIdleAngularDamping, SetShuttleIdleAngularDamping, true);
|
||||
configManager.OnValueChanged(CCVars.ShuttleMaxAngularAcc, SetShuttleMaxAngularAcc, true);
|
||||
configManager.OnValueChanged(CCVars.ShuttleMaxAngularMomentum, SetShuttleMaxAngularMomentum, true);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -71,23 +57,11 @@ namespace Content.Server.Shuttles.Systems
|
||||
CleanupHyperspace();
|
||||
}
|
||||
|
||||
private void SetShuttleMaxLinearSpeed(float value) => ShuttleMaxLinearSpeed = value;
|
||||
private void SetShuttleMaxAngularSpeed(float value) => ShuttleMaxAngularSpeed = value;
|
||||
private void SetShuttleMaxAngularAcc(float value) => ShuttleMaxAngularAcc = value;
|
||||
private void SetShuttleMaxAngularMomentum(float value) => ShuttleMaxAngularMomentum = value;
|
||||
private void SetShuttleIdleLinearDamping(float value) => ShuttleIdleLinearDamping = value;
|
||||
private void SetShuttleIdleAngularDamping(float value) => ShuttleIdleAngularDamping = value;
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
ShutdownEscape();
|
||||
ShutdownEmergencyConsole();
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleMaxLinearSpeed, SetShuttleMaxLinearSpeed);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleMaxAngularSpeed, SetShuttleMaxAngularSpeed);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleIdleLinearDamping, SetShuttleIdleLinearDamping);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleIdleAngularDamping, SetShuttleIdleAngularDamping);
|
||||
_configManager.UnsubValueChanged(CCVars.ShuttleMaxAngularMomentum, SetShuttleMaxAngularMomentum);
|
||||
}
|
||||
|
||||
private void OnShuttleAdd(EntityUid uid, ShuttleComponent component, ComponentAdd args)
|
||||
@@ -159,8 +133,8 @@ namespace Content.Server.Shuttles.Systems
|
||||
component.BodyType = BodyType.Dynamic;
|
||||
component.BodyStatus = BodyStatus.InAir;
|
||||
component.FixedRotation = false;
|
||||
component.LinearDamping = ShuttleIdleLinearDamping;
|
||||
component.AngularDamping = ShuttleIdleAngularDamping;
|
||||
component.LinearDamping = ShuttleLinearDamping;
|
||||
component.AngularDamping = ShuttleAngularDamping;
|
||||
}
|
||||
|
||||
private void Disable(PhysicsComponent component)
|
||||
|
||||
@@ -869,23 +869,6 @@ namespace Content.Shared.CCVar
|
||||
/*
|
||||
* Shuttles
|
||||
*/
|
||||
public static readonly CVarDef<float> ShuttleMaxLinearSpeed =
|
||||
CVarDef.Create("shuttle.max_linear_speed", 13f, CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<float> ShuttleMaxAngularSpeed =
|
||||
CVarDef.Create("shuttle.max_angular_speed", 1.4f, CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<float> ShuttleMaxAngularAcc =
|
||||
CVarDef.Create("shuttle.max_angular_acc", 2f, CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<float> ShuttleMaxAngularMomentum =
|
||||
CVarDef.Create("shuttle.max_angular_momentum", 60000f, CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<float> ShuttleIdleLinearDamping =
|
||||
CVarDef.Create("shuttle.idle_linear_damping", 50f, CVar.SERVERONLY);
|
||||
|
||||
public static readonly CVarDef<float> ShuttleIdleAngularDamping =
|
||||
CVarDef.Create("shuttle.idle_angular_damping", 100f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether cargo shuttles are enabled.
|
||||
|
||||
@@ -22,18 +22,18 @@ public abstract partial class SharedMoverController
|
||||
|
||||
if (_pushingEnabled)
|
||||
{
|
||||
_physics.KinematicControllerCollision += OnMobCollision;
|
||||
PhysicsSystem.KinematicControllerCollision += OnMobCollision;
|
||||
}
|
||||
else
|
||||
{
|
||||
_physics.KinematicControllerCollision -= OnMobCollision;
|
||||
PhysicsSystem.KinematicControllerCollision -= OnMobCollision;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShutdownPushing()
|
||||
{
|
||||
if (_pushingEnabled)
|
||||
_physics.KinematicControllerCollision -= OnMobCollision;
|
||||
PhysicsSystem.KinematicControllerCollision -= OnMobCollision;
|
||||
|
||||
_configManager.UnsubValueChanged(CCVars.MobPushing, SetPushing);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Content.Shared.Movement.Systems
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedMobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
|
||||
private const float StepSoundMoveDistanceRunning = 2;
|
||||
@@ -131,7 +130,7 @@ namespace Content.Shared.Movement.Systems
|
||||
touching = ev.CanMove;
|
||||
|
||||
if (!touching && TryComp<MobMoverComponent>(xform.Owner, out var mobMover))
|
||||
touching |= IsAroundCollider(_physics, xform, mobMover, physicsComponent);
|
||||
touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent);
|
||||
}
|
||||
|
||||
if (!touching)
|
||||
@@ -227,7 +226,7 @@ namespace Content.Shared.Movement.Systems
|
||||
if (!weightless || touching)
|
||||
Accelerate(ref velocity, in worldTotal, accel, frameTime);
|
||||
|
||||
_physics.SetLinearVelocity(physicsComponent, velocity);
|
||||
PhysicsSystem.SetLinearVelocity(physicsComponent, velocity);
|
||||
}
|
||||
|
||||
private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity)
|
||||
|
||||
Reference in New Issue
Block a user