diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index bac19334fc..6e37d22a4c 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -66,7 +66,7 @@ namespace Content.Client.Physics.Controllers // Server-side should just be handled on its own so we'll just do this shizznit if (TryComp(player, out IMobMoverComponent? mobMover)) { - HandleMobMovement(mover, body, mobMover, xform); + HandleMobMovement(mover, body, mobMover, xform, frameTime); return; } diff --git a/Content.Server/AI/Components/AiControllerComponent.cs b/Content.Server/AI/Components/AiControllerComponent.cs index d6eb2a9fc7..7cbc9eecdc 100644 --- a/Content.Server/AI/Components/AiControllerComponent.cs +++ b/Content.Server/AI/Components/AiControllerComponent.cs @@ -95,9 +95,6 @@ namespace Content.Server.AI.Components [ViewVariables(VVAccess.ReadWrite)] public float PushStrength { get; set; } = IMobMoverComponent.PushStrengthDefault; - [ViewVariables(VVAccess.ReadWrite)] - public float WeightlessStrength { get; set; } = IMobMoverComponent.WeightlessStrengthDefault; - /// [ViewVariables(VVAccess.ReadWrite)] public float GrabRange { get; set; } = IMobMoverComponent.GrabRangeDefault; diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index 4b2c7c6212..f29fc79566 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -42,7 +42,7 @@ namespace Content.Server.Physics.Controllers foreach (var (mobMover, mover, physics, xform) in EntityManager.EntityQuery()) { _excludedMobs.Add(mover.Owner); - HandleMobMovement(mover, physics, mobMover, xform); + HandleMobMovement(mover, physics, mobMover, xform, frameTime); } HandleShuttleMovement(frameTime); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 1e15e71157..f43c8f24cc 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -380,6 +380,54 @@ namespace Content.Shared.CCVar * Physics */ + /* + * WARNING: These are liable to get changed to datafields whenever movement refactor occurs and may no longer be valid. + * You were warned! + */ + + /// + /// Minimum speed a mob has to be moving before applying movement friction. + /// + public static readonly CVarDef MinimumFrictionSpeed = + CVarDef.Create("physics.minimum_friction_speed", 0.005f, CVar.ARCHIVE | CVar.REPLICATED); + + /// + /// The acceleration applied to mobs when moving. + /// + public static readonly CVarDef MobAcceleration = + CVarDef.Create("physics.mob_acceleration", 14f, CVar.ARCHIVE | CVar.REPLICATED); + + /// + /// The negative velocity applied for friction. + /// + public static readonly CVarDef MobFriction = + CVarDef.Create("physics.mob_friction", 14f, CVar.ARCHIVE | CVar.REPLICATED); + + /// + /// The acceleration applied to mobs when moving and weightless. + /// + public static readonly CVarDef MobWeightlessAcceleration = + CVarDef.Create("physics.mob_weightless_acceleration", 1f, CVar.ARCHIVE | CVar.REPLICATED); + + /// + /// The negative velocity applied for friction when weightless and providing inputs. + /// + public static readonly CVarDef MobWeightlessFriction = + CVarDef.Create("physics.mob_weightless_friction", 1f, CVar.ARCHIVE | CVar.REPLICATED); + + /// + /// The negative velocity applied for friction when weightless and not providing inputs. + /// This is essentially how much their speed decreases per second. + /// + public static readonly CVarDef MobWeightlessFrictionNoInput = + CVarDef.Create("physics.mob_weightless_friction_no_input", 0.2f, CVar.ARCHIVE | CVar.REPLICATED); + + /// + /// The movement speed modifier applied to a mob's total input velocity when weightless. + /// + public static readonly CVarDef MobWeightlessModifier = + CVarDef.Create("physics.mob_weightless_modifier", 0.7f, CVar.ARCHIVE | CVar.REPLICATED); + /// /// When a mob is walking should its X / Y movement be relative to its parent (true) or the map (false). /// @@ -387,10 +435,10 @@ namespace Content.Shared.CCVar CVarDef.Create("physics.relative_movement", true, CVar.ARCHIVE | CVar.REPLICATED); public static readonly CVarDef TileFrictionModifier = - CVarDef.Create("physics.tile_friction", 40.0f); + CVarDef.Create("physics.tile_friction", 40.0f, CVar.ARCHIVE | CVar.REPLICATED); public static readonly CVarDef StopSpeed = - CVarDef.Create("physics.stop_speed", 0.1f); + CVarDef.Create("physics.stop_speed", 0.1f, CVar.ARCHIVE | CVar.REPLICATED); /// /// Whether mobs can push objects like lockers. diff --git a/Content.Shared/Movement/Components/IMobMoverComponent.cs b/Content.Shared/Movement/Components/IMobMoverComponent.cs index 4c22a181d6..d383738f03 100644 --- a/Content.Shared/Movement/Components/IMobMoverComponent.cs +++ b/Content.Shared/Movement/Components/IMobMoverComponent.cs @@ -15,7 +15,5 @@ namespace Content.Shared.Movement.Components float GrabRange { get; set; } float PushStrength { get; set; } - - float WeightlessStrength { get; set; } } } diff --git a/Content.Shared/Movement/Components/SharedPlayerMobMoverComponent.cs b/Content.Shared/Movement/Components/SharedPlayerMobMoverComponent.cs index f7365dd7c2..4e1713432e 100644 --- a/Content.Shared/Movement/Components/SharedPlayerMobMoverComponent.cs +++ b/Content.Shared/Movement/Components/SharedPlayerMobMoverComponent.cs @@ -18,9 +18,6 @@ namespace Content.Shared.Movement.Components [DataField("pushStrength")] private float _pushStrength = IMobMoverComponent.PushStrengthDefault; - [DataField("weightlessStrength")] - private float _weightlessStrength = IMobMoverComponent.WeightlessStrengthDefault; - [ViewVariables(VVAccess.ReadWrite)] public EntityCoordinates LastPosition { get; set; } @@ -62,18 +59,6 @@ namespace Content.Shared.Movement.Components } } - [ViewVariables(VVAccess.ReadWrite)] - public float WeightlessStrength - { - get => _weightlessStrength; - set - { - if (MathHelper.CloseToPercent(_weightlessStrength, value)) return; - _weightlessStrength = value; - Dirty(); - } - } - protected override void Initialize() { base.Initialize(); @@ -85,7 +70,7 @@ namespace Content.Shared.Movement.Components public override ComponentState GetComponentState() { - return new PlayerMobMoverComponentState(_grabRange, _pushStrength, _weightlessStrength); + return new PlayerMobMoverComponentState(_grabRange, _pushStrength); } public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) @@ -101,13 +86,11 @@ namespace Content.Shared.Movement.Components { public float GrabRange; public float PushStrength; - public float WeightlessStrength; - public PlayerMobMoverComponentState(float grabRange, float pushStrength, float weightlessStrength) + public PlayerMobMoverComponentState(float grabRange, float pushStrength) { GrabRange = grabRange; PushStrength = pushStrength; - WeightlessStrength = weightlessStrength; } } } diff --git a/Content.Shared/Movement/SharedMoverController.cs b/Content.Shared/Movement/SharedMoverController.cs index 9829c05183..8139d34ff6 100644 --- a/Content.Shared/Movement/SharedMoverController.cs +++ b/Content.Shared/Movement/SharedMoverController.cs @@ -36,6 +36,46 @@ namespace Content.Shared.Movement private const float FootstepVariation = 0f; private const float FootstepVolume = 1f; + /// + /// + /// + private float _minimumFrictionSpeed; + + /// + /// + /// + private float _stopSpeed; + + /// + /// + /// + private float _mobAcceleration; + + /// + /// + /// + private float _frictionVelocity; + + /// + /// + /// + private float _mobWeightlessAcceleration; + + /// + /// + /// + private float _weightlessFrictionVelocity; + + /// + /// + /// + private float _weightlessFrictionVelocityNoInput; + + /// + /// + /// + private float _mobWeightlessModifier; + private bool _relativeMovement; /// @@ -47,17 +87,42 @@ namespace Content.Shared.Movement { base.Initialize(); var configManager = IoCManager.Resolve(); + // Hello configManager.OnValueChanged(CCVars.RelativeMovement, SetRelativeMovement, true); + configManager.OnValueChanged(CCVars.MinimumFrictionSpeed, SetMinimumFrictionSpeed, true); + configManager.OnValueChanged(CCVars.MobFriction, SetFrictionVelocity, true); + configManager.OnValueChanged(CCVars.MobWeightlessFriction, SetWeightlessFrictionVelocity, true); + configManager.OnValueChanged(CCVars.StopSpeed, SetStopSpeed, true); + configManager.OnValueChanged(CCVars.MobAcceleration, SetMobAcceleration, true); + configManager.OnValueChanged(CCVars.MobWeightlessAcceleration, SetMobWeightlessAcceleration, true); + configManager.OnValueChanged(CCVars.MobWeightlessFrictionNoInput, SetWeightlessFrictionNoInput, true); + configManager.OnValueChanged(CCVars.MobWeightlessModifier, SetMobWeightlessModifier, true); UpdatesBefore.Add(typeof(SharedTileFrictionController)); } private void SetRelativeMovement(bool value) => _relativeMovement = value; + private void SetMinimumFrictionSpeed(float value) => _minimumFrictionSpeed = value; + private void SetStopSpeed(float value) => _stopSpeed = value; + private void SetFrictionVelocity(float value) => _frictionVelocity = value; + private void SetWeightlessFrictionVelocity(float value) => _weightlessFrictionVelocity = value; + private void SetMobAcceleration(float value) => _mobAcceleration = value; + private void SetMobWeightlessAcceleration(float value) => _mobWeightlessAcceleration = value; + private void SetWeightlessFrictionNoInput(float value) => _weightlessFrictionVelocityNoInput = value; + private void SetMobWeightlessModifier(float value) => _mobWeightlessModifier = value; public override void Shutdown() { base.Shutdown(); var configManager = IoCManager.Resolve(); configManager.UnsubValueChanged(CCVars.RelativeMovement, SetRelativeMovement); + configManager.UnsubValueChanged(CCVars.MinimumFrictionSpeed, SetMinimumFrictionSpeed); + configManager.UnsubValueChanged(CCVars.StopSpeed, SetStopSpeed); + configManager.UnsubValueChanged(CCVars.MobFriction, SetFrictionVelocity); + configManager.UnsubValueChanged(CCVars.MobWeightlessFriction, SetWeightlessFrictionVelocity); + configManager.UnsubValueChanged(CCVars.MobAcceleration, SetMobAcceleration); + configManager.UnsubValueChanged(CCVars.MobWeightlessAcceleration, SetMobWeightlessAcceleration); + configManager.UnsubValueChanged(CCVars.MobWeightlessFrictionNoInput, SetWeightlessFrictionNoInput); + configManager.UnsubValueChanged(CCVars.MobWeightlessModifier, SetMobWeightlessModifier); } public override void UpdateAfterSolve(bool prediction, float frameTime) @@ -108,7 +173,8 @@ namespace Content.Shared.Movement IMoverComponent mover, PhysicsComponent physicsComponent, IMobMoverComponent mobMover, - TransformComponent xform) + TransformComponent xform, + float frameTime) { DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner)); @@ -121,20 +187,18 @@ namespace Content.Shared.Movement UsedMobMovement[mover.Owner] = true; var weightless = mover.Owner.IsWeightless(physicsComponent, mapManager: _mapManager, entityManager: EntityManager); var (walkDir, sprintDir) = mover.VelocityDir; + bool touching = true; // Handle wall-pushes. if (weightless) { // No gravity: is our entity touching anything? - var touching = IsAroundCollider(_physics, xform, mobMover, physicsComponent); + touching = xform.GridUid != null || IsAroundCollider(_physics, xform, mobMover, physicsComponent); if (!touching) { - if (xform.GridUid != EntityUid.Invalid) + if (xform.GridUid != null) mover.LastGridAngle = GetParentGridAngle(xform, mover); - - xform.WorldRotation = physicsComponent.LinearVelocity.GetDir().ToAngle(); - return; } } @@ -142,15 +206,34 @@ namespace Content.Shared.Movement // Target velocity. // This is relative to the map / grid we're on. var total = walkDir * mover.CurrentWalkSpeed + sprintDir * mover.CurrentSprintSpeed; - var parentRotation = GetParentGridAngle(xform, mover); - var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total; DebugTools.Assert(MathHelper.CloseToPercent(total.Length, worldTotal.Length)); + var velocity = physicsComponent.LinearVelocity; + float friction; + float weightlessModifier; + float accel; + if (weightless) - worldTotal *= mobMover.WeightlessStrength; + { + if (worldTotal != Vector2.Zero && touching) + friction = _weightlessFrictionVelocity; + else + friction = _weightlessFrictionVelocityNoInput; + + weightlessModifier = _mobWeightlessModifier; + accel = _mobWeightlessAcceleration; + } + else + { + friction = _frictionVelocity; + weightlessModifier = 1f; + accel = _mobAcceleration; + } + + Friction(frameTime, friction, ref velocity); if (xform.GridUid != EntityUid.Invalid) mover.LastGridAngle = parentRotation; @@ -172,7 +255,47 @@ namespace Content.Shared.Movement } } - _physics.SetLinearVelocity(physicsComponent, worldTotal); + worldTotal *= weightlessModifier; + + if (touching) + Accelerate(ref velocity, in worldTotal, accel, frameTime); + + _physics.SetLinearVelocity(physicsComponent, velocity); + } + + private void Friction(float frameTime, float friction, ref Vector2 velocity) + { + var speed = velocity.Length; + + if (speed < _minimumFrictionSpeed) return; + + var drop = 0f; + + var control = MathF.Max(_stopSpeed, speed); + drop += control * friction * frameTime; + + var newSpeed = MathF.Max(0f, speed - drop); + + if (newSpeed.Equals(speed)) return; + + newSpeed /= speed; + velocity *= newSpeed; + } + + private void Accelerate(ref Vector2 currentVelocity, in Vector2 velocity, float accel, float frameTime) + { + var wishDir = velocity != Vector2.Zero ? velocity.Normalized : Vector2.Zero; + var wishSpeed = velocity.Length; + + var currentSpeed = Vector2.Dot(currentVelocity, wishDir); + var addSpeed = wishSpeed - currentSpeed; + + if (addSpeed <= 0f) return; + + var accelSpeed = accel * frameTime * wishSpeed; + accelSpeed = MathF.Min(accelSpeed, addSpeed); + + currentVelocity += wishDir * accelSpeed; } public bool UseMobMovement(EntityUid uid) diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index eab04a9c4b..d049ee013e 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Interaction; +using Content.Shared.Movement.Components; using Content.Shared.Tag; using Robust.Shared.Physics; using Robust.Shared.Timing; @@ -93,7 +94,10 @@ public sealed class ThrowingSystem : EntitySystem } // Give thrower an impulse in the other direction - if (user != null && pushbackRatio > 0.0f && physicsQuery.Value.TryGetComponent(user.Value, out var userPhysics)) + if (user != null && + pushbackRatio > 0.0f && + physicsQuery.Value.TryGetComponent(user.Value, out var userPhysics) && + user.Value.IsWeightless(userPhysics, entityManager: EntityManager)) { var msg = new ThrowPushbackAttemptEvent(); RaiseLocalEvent(physics.Owner, msg, false);