From 1c6b9aaae7b2dd64ca7e53aeeeb8ebb1438adb4a Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Thu, 12 Sep 2024 22:01:36 +0200 Subject: [PATCH] movercontroller namespace (#31749) --- .../Physics/Controllers/MoverController.cs | 201 +++-- .../Physics/Controllers/MoverController.cs | 849 +++++++++--------- .../Movement/Systems/SharedMoverController.cs | 761 ++++++++-------- 3 files changed, 904 insertions(+), 907 deletions(-) diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 03df383eebce1b..c97110b208e58f 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -8,132 +8,131 @@ using Robust.Shared.Player; using Robust.Shared.Timing; -namespace Content.Client.Physics.Controllers +namespace Content.Client.Physics.Controllers; + +public sealed class MoverController : SharedMoverController { - public sealed class MoverController : SharedMoverController - { - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnRelayPlayerAttached); - SubscribeLocalEvent(OnRelayPlayerDetached); - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnPlayerDetached); - - SubscribeLocalEvent(OnUpdatePredicted); - SubscribeLocalEvent(OnUpdateRelayTargetPredicted); - SubscribeLocalEvent(OnUpdatePullablePredicted); - } + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRelayPlayerAttached); + SubscribeLocalEvent(OnRelayPlayerDetached); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeLocalEvent(OnUpdatePredicted); + SubscribeLocalEvent(OnUpdateRelayTargetPredicted); + SubscribeLocalEvent(OnUpdatePullablePredicted); + } - private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args) - { - // Enable prediction if an entity is controlled by the player - if (entity.Owner == _playerManager.LocalEntity) - args.IsPredicted = true; - } + private void OnUpdatePredicted(Entity entity, ref UpdateIsPredictedEvent args) + { + // Enable prediction if an entity is controlled by the player + if (entity.Owner == _playerManager.LocalEntity) + args.IsPredicted = true; + } - private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args) - { - if (entity.Comp.Source == _playerManager.LocalEntity) - args.IsPredicted = true; - } + private void OnUpdateRelayTargetPredicted(Entity entity, ref UpdateIsPredictedEvent args) + { + if (entity.Comp.Source == _playerManager.LocalEntity) + args.IsPredicted = true; + } - private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args) - { - // Enable prediction if an entity is being pulled by the player. - // Disable prediction if an entity is being pulled by some non-player entity. + private void OnUpdatePullablePredicted(Entity entity, ref UpdateIsPredictedEvent args) + { + // Enable prediction if an entity is being pulled by the player. + // Disable prediction if an entity is being pulled by some non-player entity. - if (entity.Comp.Puller == _playerManager.LocalEntity) - args.IsPredicted = true; - else if (entity.Comp.Puller != null) - args.BlockPrediction = true; + if (entity.Comp.Puller == _playerManager.LocalEntity) + args.IsPredicted = true; + else if (entity.Comp.Puller != null) + args.BlockPrediction = true; - // TODO recursive pulling checks? - // What if the entity is being pulled by a vehicle controlled by the player? - } + // TODO recursive pulling checks? + // What if the entity is being pulled by a vehicle controlled by the player? + } - private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) - { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); - if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); - } + private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) + { + Physics.UpdateIsPredicted(entity.Owner); + Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); + } - private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) - { - Physics.UpdateIsPredicted(entity.Owner); - Physics.UpdateIsPredicted(entity.Comp.RelayEntity); - if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); - } + private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) + { + Physics.UpdateIsPredicted(entity.Owner); + Physics.UpdateIsPredicted(entity.Comp.RelayEntity); + if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); + } - private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) - { - SetMoveInput(entity, MoveButtons.None); - } + private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) + { + SetMoveInput(entity, MoveButtons.None); + } - private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) - { - SetMoveInput(entity, MoveButtons.None); - } + private void OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) + { + SetMoveInput(entity, MoveButtons.None); + } - public override void UpdateBeforeSolve(bool prediction, float frameTime) - { - base.UpdateBeforeSolve(prediction, frameTime); + public override void UpdateBeforeSolve(bool prediction, float frameTime) + { + base.UpdateBeforeSolve(prediction, frameTime); - if (_playerManager.LocalEntity is not {Valid: true} player) - return; + if (_playerManager.LocalEntity is not {Valid: true} player) + return; - if (RelayQuery.TryGetComponent(player, out var relayMover)) - HandleClientsideMovement(relayMover.RelayEntity, frameTime); + if (RelayQuery.TryGetComponent(player, out var relayMover)) + HandleClientsideMovement(relayMover.RelayEntity, frameTime); - HandleClientsideMovement(player, frameTime); - } + HandleClientsideMovement(player, frameTime); + } - private void HandleClientsideMovement(EntityUid player, float frameTime) + private void HandleClientsideMovement(EntityUid player, float frameTime) + { + if (!MoverQuery.TryGetComponent(player, out var mover) || + !XformQuery.TryGetComponent(player, out var xform)) { - if (!MoverQuery.TryGetComponent(player, out var mover) || - !XformQuery.TryGetComponent(player, out var xform)) - { - return; - } - - var physicsUid = player; - PhysicsComponent? body; - var xformMover = xform; + return; + } - if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) - { - if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || - !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) - { - return; - } + var physicsUid = player; + PhysicsComponent? body; + var xformMover = xform; - physicsUid = xform.ParentUid; - } - else if (!PhysicsQuery.TryGetComponent(player, out body)) + if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) + { + if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || + !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) { return; } - // Server-side should just be handled on its own so we'll just do this shizznit - HandleMobMovement( - player, - mover, - physicsUid, - body, - xformMover, - frameTime); + physicsUid = xform.ParentUid; } - - protected override bool CanSound() + else if (!PhysicsQuery.TryGetComponent(player, out body)) { - return _timing is { IsFirstTimePredicted: true, InSimulation: true }; + return; } + + // Server-side should just be handled on its own so we'll just do this shizznit + HandleMobMovement( + player, + mover, + physicsUid, + body, + xformMover, + frameTime); + } + + protected override bool CanSound() + { + return _timing is { IsFirstTimePredicted: true, InSimulation: true }; } } diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index 19d58438b35b39..f927e717a9d868 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -12,560 +12,559 @@ using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute; using Robust.Shared.Map.Components; -namespace Content.Server.Physics.Controllers +namespace Content.Server.Physics.Controllers; + +public sealed class MoverController : SharedMoverController { - public sealed class MoverController : SharedMoverController + [Dependency] private readonly ThrusterSystem _thruster = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + + private Dictionary)> _shuttlePilots = new(); + + public override void Initialize() { - [Dependency] private readonly ThrusterSystem _thruster = default!; - [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + base.Initialize(); + SubscribeLocalEvent(OnRelayPlayerAttached); + SubscribeLocalEvent(OnRelayPlayerDetached); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + } - private Dictionary)> _shuttlePilots = new(); + private void OnRelayPlayerAttached(Entity entity, ref PlayerAttachedEvent args) + { + if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); + } - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnRelayPlayerAttached); - SubscribeLocalEvent(OnRelayPlayerDetached); - SubscribeLocalEvent(OnPlayerAttached); - SubscribeLocalEvent(OnPlayerDetached); - } + private void OnRelayPlayerDetached(Entity entity, ref PlayerDetachedEvent args) + { + if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); + } - private void OnRelayPlayerAttached(Entity entity, ref PlayerAttachedEvent args) - { - if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); - } + private void OnPlayerAttached(Entity entity, ref PlayerAttachedEvent args) + { + SetMoveInput(entity, MoveButtons.None); + } - private void OnRelayPlayerDetached(Entity entity, ref PlayerDetachedEvent args) - { - if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); - } + private void OnPlayerDetached(Entity entity, ref PlayerDetachedEvent args) + { + SetMoveInput(entity, MoveButtons.None); + } - private void OnPlayerAttached(Entity entity, ref PlayerAttachedEvent args) - { - SetMoveInput(entity, MoveButtons.None); - } + protected override bool CanSound() + { + return true; + } - private void OnPlayerDetached(Entity entity, ref PlayerDetachedEvent args) - { - SetMoveInput(entity, MoveButtons.None); - } + public override void UpdateBeforeSolve(bool prediction, float frameTime) + { + base.UpdateBeforeSolve(prediction, frameTime); - protected override bool CanSound() - { - return true; - } + var inputQueryEnumerator = AllEntityQuery(); - public override void UpdateBeforeSolve(bool prediction, float frameTime) + while (inputQueryEnumerator.MoveNext(out var uid, out var mover)) { - base.UpdateBeforeSolve(prediction, frameTime); + var physicsUid = uid; - var inputQueryEnumerator = AllEntityQuery(); + if (RelayQuery.HasComponent(uid)) + continue; - while (inputQueryEnumerator.MoveNext(out var uid, out var mover)) + if (!XformQuery.TryGetComponent(uid, out var xform)) { - var physicsUid = uid; - - if (RelayQuery.HasComponent(uid)) - continue; - - if (!XformQuery.TryGetComponent(uid, out var xform)) - { - continue; - } - - PhysicsComponent? body; - var xformMover = xform; + continue; + } - if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) - { - if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || - !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) - { - continue; - } + PhysicsComponent? body; + var xformMover = xform; - physicsUid = xform.ParentUid; - } - else if (!PhysicsQuery.TryGetComponent(uid, out body)) + if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid)) + { + if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) || + !XformQuery.TryGetComponent(xform.ParentUid, out xformMover)) { continue; } - HandleMobMovement(uid, - mover, - physicsUid, - body, - xformMover, - frameTime); + physicsUid = xform.ParentUid; } - - HandleShuttleMovement(frameTime); - } - - public (Vector2 Strafe, float Rotation, float Brakes) GetPilotVelocityInput(PilotComponent component) - { - if (!Timing.InSimulation) + else if (!PhysicsQuery.TryGetComponent(uid, out body)) { - // Outside of simulation we'll be running client predicted movement per-frame. - // So return a full-length vector as if it's a full tick. - // Physics system will have the correct time step anyways. - ResetSubtick(component); - ApplyTick(component, 1f); - return (component.CurTickStrafeMovement, component.CurTickRotationMovement, component.CurTickBraking); + continue; } - float remainingFraction; - - if (Timing.CurTick > component.LastInputTick) - { - component.CurTickStrafeMovement = Vector2.Zero; - component.CurTickRotationMovement = 0f; - component.CurTickBraking = 0f; - remainingFraction = 1; - } - else - { - remainingFraction = (ushort.MaxValue - component.LastInputSubTick) / (float) ushort.MaxValue; - } + HandleMobMovement(uid, + mover, + physicsUid, + body, + xformMover, + frameTime); + } - ApplyTick(component, remainingFraction); + HandleShuttleMovement(frameTime); + } - // Logger.Info($"{curDir}{walk}{sprint}"); + public (Vector2 Strafe, float Rotation, float Brakes) GetPilotVelocityInput(PilotComponent component) + { + if (!Timing.InSimulation) + { + // Outside of simulation we'll be running client predicted movement per-frame. + // So return a full-length vector as if it's a full tick. + // Physics system will have the correct time step anyways. + ResetSubtick(component); + ApplyTick(component, 1f); return (component.CurTickStrafeMovement, component.CurTickRotationMovement, component.CurTickBraking); } - private void ResetSubtick(PilotComponent component) - { - if (Timing.CurTick <= component.LastInputTick) return; + float remainingFraction; + if (Timing.CurTick > component.LastInputTick) + { component.CurTickStrafeMovement = Vector2.Zero; component.CurTickRotationMovement = 0f; component.CurTickBraking = 0f; - component.LastInputTick = Timing.CurTick; - component.LastInputSubTick = 0; + remainingFraction = 1; + } + else + { + remainingFraction = (ushort.MaxValue - component.LastInputSubTick) / (float) ushort.MaxValue; } - protected override void HandleShuttleInput(EntityUid uid, ShuttleButtons button, ushort subTick, bool state) + ApplyTick(component, remainingFraction); + + // Logger.Info($"{curDir}{walk}{sprint}"); + return (component.CurTickStrafeMovement, component.CurTickRotationMovement, component.CurTickBraking); + } + + private void ResetSubtick(PilotComponent component) + { + if (Timing.CurTick <= component.LastInputTick) return; + + component.CurTickStrafeMovement = Vector2.Zero; + component.CurTickRotationMovement = 0f; + component.CurTickBraking = 0f; + component.LastInputTick = Timing.CurTick; + component.LastInputSubTick = 0; + } + + protected override void HandleShuttleInput(EntityUid uid, ShuttleButtons button, ushort subTick, bool state) + { + if (!TryComp(uid, out var pilot) || pilot.Console == null) + return; + + ResetSubtick(pilot); + + if (subTick >= pilot.LastInputSubTick) { - if (!TryComp(uid, out var pilot) || pilot.Console == null) - return; + var fraction = (subTick - pilot.LastInputSubTick) / (float) ushort.MaxValue; - ResetSubtick(pilot); + ApplyTick(pilot, fraction); + pilot.LastInputSubTick = subTick; + } - if (subTick >= pilot.LastInputSubTick) - { - var fraction = (subTick - pilot.LastInputSubTick) / (float) ushort.MaxValue; + var buttons = pilot.HeldButtons; - ApplyTick(pilot, fraction); - pilot.LastInputSubTick = subTick; - } + if (state) + { + buttons |= button; + } + else + { + buttons &= ~button; + } - var buttons = pilot.HeldButtons; + pilot.HeldButtons = buttons; + } - if (state) - { - buttons |= button; - } - else - { - buttons &= ~button; - } + private static void ApplyTick(PilotComponent component, float fraction) + { + var x = 0; + var y = 0; + var rot = 0; + int brake; - pilot.HeldButtons = buttons; + if ((component.HeldButtons & ShuttleButtons.StrafeLeft) != 0x0) + { + x -= 1; } - private static void ApplyTick(PilotComponent component, float fraction) + if ((component.HeldButtons & ShuttleButtons.StrafeRight) != 0x0) { - var x = 0; - var y = 0; - var rot = 0; - int brake; + x += 1; + } - if ((component.HeldButtons & ShuttleButtons.StrafeLeft) != 0x0) - { - x -= 1; - } + component.CurTickStrafeMovement.X += x * fraction; - if ((component.HeldButtons & ShuttleButtons.StrafeRight) != 0x0) - { - x += 1; - } + if ((component.HeldButtons & ShuttleButtons.StrafeUp) != 0x0) + { + y += 1; + } - component.CurTickStrafeMovement.X += x * fraction; + if ((component.HeldButtons & ShuttleButtons.StrafeDown) != 0x0) + { + y -= 1; + } - if ((component.HeldButtons & ShuttleButtons.StrafeUp) != 0x0) - { - y += 1; - } + component.CurTickStrafeMovement.Y += y * fraction; - if ((component.HeldButtons & ShuttleButtons.StrafeDown) != 0x0) - { - y -= 1; - } + if ((component.HeldButtons & ShuttleButtons.RotateLeft) != 0x0) + { + rot -= 1; + } - component.CurTickStrafeMovement.Y += y * fraction; + if ((component.HeldButtons & ShuttleButtons.RotateRight) != 0x0) + { + rot += 1; + } - if ((component.HeldButtons & ShuttleButtons.RotateLeft) != 0x0) - { - rot -= 1; - } + component.CurTickRotationMovement += rot * fraction; - if ((component.HeldButtons & ShuttleButtons.RotateRight) != 0x0) - { - rot += 1; - } + if ((component.HeldButtons & ShuttleButtons.Brake) != 0x0) + { + brake = 1; + } + else + { + brake = 0; + } - component.CurTickRotationMovement += rot * fraction; + 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 = vel.X != 0 ? MathF.Pow(Vector2.Dot(vel, new (shuttle.LinearThrust[horizIndex] / shuttle.LinearThrust[horizIndex], 0f)), 2) : 0; + var vertComp = vel.Y != 0 ? MathF.Pow(Vector2.Dot(vel, new (0f, shuttle.LinearThrust[vertIndex] / shuttle.LinearThrust[vertIndex])), 2) : 0; + + return shuttle.BaseMaxLinearVelocity * vel * MathF.ReciprocalSqrtEstimate(horizComp + vertComp); + } + + private void HandleShuttleMovement(float frameTime) + { + var newPilots = new Dictionary)>(); + + // We just mark off their movement and the shuttle itself does its own movement + var activePilotQuery = EntityQueryEnumerator(); + var shuttleQuery = GetEntityQuery(); + while (activePilotQuery.MoveNext(out var uid, out var pilot, out var mover)) + { + var consoleEnt = pilot.Console; - if ((component.HeldButtons & ShuttleButtons.Brake) != 0x0) + // TODO: This is terrible. Just make a new mover and also make it remote piloting + device networks + if (TryComp(consoleEnt, out var cargoConsole)) { - brake = 1; + consoleEnt = cargoConsole.Entity; } - else + + if (!TryComp(consoleEnt, out TransformComponent? xform)) continue; + + var gridId = xform.GridUid; + // This tries to see if the grid is a shuttle and if the console should work. + if (!TryComp(gridId, out var _) || + !shuttleQuery.TryGetComponent(gridId, out var shuttleComponent) || + !shuttleComponent.Enabled) + continue; + + if (!newPilots.TryGetValue(gridId!.Value, out var pilots)) { - brake = 0; + pilots = (shuttleComponent, new List<(EntityUid, PilotComponent, InputMoverComponent, TransformComponent)>()); + newPilots[gridId.Value] = pilots; } - component.CurTickBraking += brake * fraction; + pilots.Item2.Add((uid, pilot, mover, xform)); } - /// - /// Helper function to extrapolate max velocity for a given Vector2 (really, its angle) and shuttle. - /// - private Vector2 ObtainMaxVel(Vector2 vel, ShuttleComponent shuttle) + // Reset inputs for non-piloted shuttles. + foreach (var (shuttleUid, (shuttle, _)) in _shuttlePilots) { - 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 = vel.X != 0 ? MathF.Pow(Vector2.Dot(vel, new (shuttle.LinearThrust[horizIndex] / shuttle.LinearThrust[horizIndex], 0f)), 2) : 0; - var vertComp = vel.Y != 0 ? MathF.Pow(Vector2.Dot(vel, new (0f, shuttle.LinearThrust[vertIndex] / shuttle.LinearThrust[vertIndex])), 2) : 0; - - return shuttle.BaseMaxLinearVelocity * vel * MathF.ReciprocalSqrtEstimate(horizComp + vertComp); + if (newPilots.ContainsKey(shuttleUid) || CanPilot(shuttleUid)) + continue; + + _thruster.DisableLinearThrusters(shuttle); } - private void HandleShuttleMovement(float frameTime) + _shuttlePilots = newPilots; + + // Collate all of the linear / angular velocites for a shuttle + // then do the movement input once for it. + var xformQuery = GetEntityQuery(); + foreach (var (shuttleUid, (shuttle, pilots)) in _shuttlePilots) { - var newPilots = new Dictionary)>(); + if (Paused(shuttleUid) || CanPilot(shuttleUid) || !TryComp(shuttleUid, out var body)) + continue; - // We just mark off their movement and the shuttle itself does its own movement - var activePilotQuery = EntityQueryEnumerator(); - var shuttleQuery = GetEntityQuery(); - while (activePilotQuery.MoveNext(out var uid, out var pilot, out var mover)) + var shuttleNorthAngle = _xformSystem.GetWorldRotation(shuttleUid, xformQuery); + + // Collate movement linear and angular inputs together + var linearInput = Vector2.Zero; + var brakeInput = 0f; + var angularInput = 0f; + + foreach (var (pilotUid, pilot, _, consoleXform) in pilots) { - var consoleEnt = pilot.Console; + var (strafe, rotation, brakes) = GetPilotVelocityInput(pilot); - // TODO: This is terrible. Just make a new mover and also make it remote piloting + device networks - if (TryComp(consoleEnt, out var cargoConsole)) + if (brakes > 0f) { - consoleEnt = cargoConsole.Entity; + brakeInput += brakes; } - if (!TryComp(consoleEnt, out TransformComponent? xform)) continue; - - var gridId = xform.GridUid; - // This tries to see if the grid is a shuttle and if the console should work. - if (!TryComp(gridId, out var _) || - !shuttleQuery.TryGetComponent(gridId, out var shuttleComponent) || - !shuttleComponent.Enabled) - continue; - - if (!newPilots.TryGetValue(gridId!.Value, out var pilots)) + if (strafe.Length() > 0f) { - pilots = (shuttleComponent, new List<(EntityUid, PilotComponent, InputMoverComponent, TransformComponent)>()); - newPilots[gridId.Value] = pilots; + var offsetRotation = consoleXform.LocalRotation; + linearInput += offsetRotation.RotateVec(strafe); } - pilots.Item2.Add((uid, pilot, mover, xform)); - } - - // Reset inputs for non-piloted shuttles. - foreach (var (shuttleUid, (shuttle, _)) in _shuttlePilots) - { - if (newPilots.ContainsKey(shuttleUid) || CanPilot(shuttleUid)) - continue; - - _thruster.DisableLinearThrusters(shuttle); + if (rotation != 0f) + { + angularInput += rotation; + } } - _shuttlePilots = newPilots; + var count = pilots.Count; + linearInput /= count; + angularInput /= count; + brakeInput /= count; - // Collate all of the linear / angular velocites for a shuttle - // then do the movement input once for it. - var xformQuery = GetEntityQuery(); - foreach (var (shuttleUid, (shuttle, pilots)) in _shuttlePilots) + // Handle shuttle movement + if (brakeInput > 0f) { - if (Paused(shuttleUid) || CanPilot(shuttleUid) || !TryComp(shuttleUid, out var body)) - continue; - - var shuttleNorthAngle = _xformSystem.GetWorldRotation(shuttleUid, xformQuery); - - // Collate movement linear and angular inputs together - var linearInput = Vector2.Zero; - var brakeInput = 0f; - var angularInput = 0f; - - foreach (var (pilotUid, pilot, _, consoleXform) in pilots) + if (body.LinearVelocity.Length() > 0f) { - var (strafe, rotation, brakes) = GetPilotVelocityInput(pilot); + // Minimum brake velocity for a direction to show its thrust appearance. + const float appearanceThreshold = 0.1f; - if (brakes > 0f) - { - brakeInput += brakes; - } - - if (strafe.Length() > 0f) - { - var offsetRotation = consoleXform.LocalRotation; - linearInput += offsetRotation.RotateVec(strafe); - } + // Get velocity relative to the shuttle so we know which thrusters to fire + var shuttleVelocity = (-shuttleNorthAngle).RotateVec(body.LinearVelocity); + var force = Vector2.Zero; - if (rotation != 0f) + if (shuttleVelocity.X < 0f) { - angularInput += rotation; - } - } + _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.West); - var count = pilots.Count; - linearInput /= count; - angularInput /= count; - brakeInput /= count; + if (shuttleVelocity.X < -appearanceThreshold) + _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.East); - // Handle shuttle movement - if (brakeInput > 0f) - { - if (body.LinearVelocity.Length() > 0f) + var index = (int) Math.Log2((int) DirectionFlag.East); + force.X += shuttle.LinearThrust[index]; + } + else if (shuttleVelocity.X > 0f) { - // Minimum brake velocity for a direction to show its thrust appearance. - const float appearanceThreshold = 0.1f; + _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.East); - // 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 > appearanceThreshold) + _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.West); - if (shuttleVelocity.X < 0f) - { - _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.West); - - if (shuttleVelocity.X < -appearanceThreshold) - _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); + var index = (int) Math.Log2((int) DirectionFlag.West); + force.X -= shuttle.LinearThrust[index]; + } - if (shuttleVelocity.X > appearanceThreshold) - _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.West); + if (shuttleVelocity.Y < 0f) + { + _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.South); - var index = (int) Math.Log2((int) DirectionFlag.West); - force.X -= shuttle.LinearThrust[index]; - } + if (shuttleVelocity.Y < -appearanceThreshold) + _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North); - if (shuttleVelocity.Y < 0f) - { - _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.South); + var index = (int) Math.Log2((int) DirectionFlag.North); + force.Y += shuttle.LinearThrust[index]; + } + else if (shuttleVelocity.Y > 0f) + { + _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.North); - if (shuttleVelocity.Y < -appearanceThreshold) - _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North); + if (shuttleVelocity.Y > appearanceThreshold) + _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South); - var index = (int) Math.Log2((int) DirectionFlag.North); - force.Y += shuttle.LinearThrust[index]; - } - else if (shuttleVelocity.Y > 0f) - { - _thruster.DisableLinearThrustDirection(shuttle, DirectionFlag.North); + var index = (int) Math.Log2((int) DirectionFlag.South); + force.Y -= shuttle.LinearThrust[index]; + } - if (shuttleVelocity.Y > appearanceThreshold) - _thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South); + var impulse = force * brakeInput * ShuttleComponent.BrakeCoefficient; + impulse = shuttleNorthAngle.RotateVec(impulse); + var forceMul = frameTime * body.InvMass; + var maxVelocity = (-body.LinearVelocity).Length() / forceMul; - var index = (int) Math.Log2((int) DirectionFlag.South); - force.Y -= shuttle.LinearThrust[index]; - } + // Don't overshoot + if (impulse.Length() > maxVelocity) + impulse = impulse.Normalized() * maxVelocity; - var impulse = force * brakeInput * ShuttleComponent.BrakeCoefficient; - impulse = shuttleNorthAngle.RotateVec(impulse); - var forceMul = frameTime * body.InvMass; - var maxVelocity = (-body.LinearVelocity).Length() / forceMul; + PhysicsSystem.ApplyForce(shuttleUid, impulse, body: body); + } + else + { + _thruster.DisableLinearThrusters(shuttle); + } - // Don't overshoot - if (impulse.Length() > maxVelocity) - impulse = impulse.Normalized() * maxVelocity; + if (body.AngularVelocity != 0f) + { + var torque = shuttle.AngularThrust * brakeInput * (body.AngularVelocity > 0f ? -1f : 1f) * ShuttleComponent.BrakeCoefficient; + var torqueMul = body.InvI * frameTime; - PhysicsSystem.ApplyForce(shuttleUid, impulse, body: body); + if (body.AngularVelocity > 0f) + { + torque = MathF.Max(-body.AngularVelocity / torqueMul, torque); } else { - _thruster.DisableLinearThrusters(shuttle); + torque = MathF.Min(-body.AngularVelocity / torqueMul, torque); } - if (body.AngularVelocity != 0f) - { - var torque = shuttle.AngularThrust * brakeInput * (body.AngularVelocity > 0f ? -1f : 1f) * ShuttleComponent.BrakeCoefficient; - var torqueMul = body.InvI * frameTime; - - if (body.AngularVelocity > 0f) - { - torque = MathF.Max(-body.AngularVelocity / torqueMul, torque); - } - else - { - torque = MathF.Min(-body.AngularVelocity / torqueMul, torque); - } - - if (!torque.Equals(0f)) - { - PhysicsSystem.ApplyTorque(shuttleUid, torque, body: body); - _thruster.SetAngularThrust(shuttle, true); - } - } - else + if (!torque.Equals(0f)) { - _thruster.SetAngularThrust(shuttle, false); + PhysicsSystem.ApplyTorque(shuttleUid, torque, body: body); + _thruster.SetAngularThrust(shuttle, true); } } - - if (linearInput.Length().Equals(0f)) + else { - PhysicsSystem.SetSleepingAllowed(shuttleUid, body, true); - - if (brakeInput.Equals(0f)) - _thruster.DisableLinearThrusters(shuttle); + _thruster.SetAngularThrust(shuttle, false); } - else + } + + if (linearInput.Length().Equals(0f)) + { + PhysicsSystem.SetSleepingAllowed(shuttleUid, body, true); + + if (brakeInput.Equals(0f)) + _thruster.DisableLinearThrusters(shuttle); + } + else + { + PhysicsSystem.SetSleepingAllowed(shuttleUid, body, false); + var angle = linearInput.ToWorldAngle(); + var linearDir = angle.GetDir(); + var dockFlag = linearDir.AsFlag(); + var totalForce = Vector2.Zero; + + // Won't just do cardinal directions. + foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag))) { - PhysicsSystem.SetSleepingAllowed(shuttleUid, body, false); - var angle = linearInput.ToWorldAngle(); - var linearDir = angle.GetDir(); - var dockFlag = linearDir.AsFlag(); - var totalForce = Vector2.Zero; - - // Won't just do cardinal directions. - foreach (DirectionFlag dir in Enum.GetValues(typeof(DirectionFlag))) + // Brain no worky but I just want cardinals + switch (dir) { - // Brain no worky but I just want cardinals - switch (dir) - { - case DirectionFlag.South: - case DirectionFlag.East: - case DirectionFlag.North: - case DirectionFlag.West: - break; - default: - continue; - } - - if ((dir & dockFlag) == 0x0) - { - _thruster.DisableLinearThrustDirection(shuttle, dir); + case DirectionFlag.South: + case DirectionFlag.East: + case DirectionFlag.North: + case DirectionFlag.West: + break; + default: continue; - } - - var force = Vector2.Zero; - var index = (int) Math.Log2((int) dir); - var thrust = shuttle.LinearThrust[index]; - - switch (dir) - { - case DirectionFlag.North: - force.Y += thrust; - break; - case DirectionFlag.South: - force.Y -= thrust; - break; - case DirectionFlag.East: - force.X += thrust; - break; - case DirectionFlag.West: - force.X -= thrust; - break; - default: - throw new ArgumentOutOfRangeException($"Attempted to apply thrust to shuttle {shuttleUid} along invalid dir {dir}."); - } - - _thruster.EnableLinearThrustDirection(shuttle, dir); - var impulse = force * linearInput.Length(); - totalForce += impulse; } - var forceMul = frameTime * body.InvMass; + if ((dir & dockFlag) == 0x0) + { + _thruster.DisableLinearThrustDirection(shuttle, dir); + continue; + } - 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; + var force = Vector2.Zero; + var index = (int) Math.Log2((int) dir); + var thrust = shuttle.LinearThrust[index]; - var finalForce = Vector2Dot(totalForce, properAccel.Normalized()) * properAccel.Normalized(); + switch (dir) + { + case DirectionFlag.North: + force.Y += thrust; + break; + case DirectionFlag.South: + force.Y -= thrust; + break; + case DirectionFlag.East: + force.X += thrust; + break; + case DirectionFlag.West: + force.X -= thrust; + break; + default: + throw new ArgumentOutOfRangeException($"Attempted to apply thrust to shuttle {shuttleUid} along invalid dir {dir}."); + } - if (localVel.Length() >= maxVelocity.Length() && Vector2.Dot(totalForce, localVel) > 0f) - finalForce = Vector2.Zero; // burn would be faster if used as such + _thruster.EnableLinearThrustDirection(shuttle, dir); + var impulse = force * linearInput.Length(); + totalForce += impulse; + } - if (finalForce.Length() > properAccel.Length()) - finalForce = properAccel; // don't overshoot + var forceMul = frameTime * body.InvMass; - //Log.Info($"shuttle: maxVelocity {maxVelocity} totalForce {totalForce} finalForce {finalForce} forceMul {forceMul} properAccel {properAccel}"); + 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; - finalForce = shuttleNorthAngle.RotateVec(finalForce); + var finalForce = Vector2Dot(totalForce, properAccel.Normalized()) * properAccel.Normalized(); - if (finalForce.Length() > 0f) - PhysicsSystem.ApplyForce(shuttleUid, finalForce, body: body); - } + if (localVel.Length() >= maxVelocity.Length() && Vector2.Dot(totalForce, localVel) > 0f) + finalForce = Vector2.Zero; // burn would be faster if used as such - if (MathHelper.CloseTo(angularInput, 0f)) - { - PhysicsSystem.SetSleepingAllowed(shuttleUid, body, true); + if (finalForce.Length() > properAccel.Length()) + finalForce = properAccel; // don't overshoot - if (brakeInput <= 0f) - _thruster.SetAngularThrust(shuttle, false); - } - else - { - PhysicsSystem.SetSleepingAllowed(shuttleUid, body, false); - var torque = shuttle.AngularThrust * -angularInput; + //Log.Info($"shuttle: maxVelocity {maxVelocity} totalForce {totalForce} finalForce {finalForce} forceMul {forceMul} properAccel {properAccel}"); - // Need to cap the velocity if 1 tick of input brings us over cap so we don't continuously - // edge onto the cap over and over. - var torqueMul = body.InvI * frameTime; + finalForce = shuttleNorthAngle.RotateVec(finalForce); - torque = Math.Clamp(torque, - (-ShuttleComponent.MaxAngularVelocity - body.AngularVelocity) / torqueMul, - (ShuttleComponent.MaxAngularVelocity - body.AngularVelocity) / torqueMul); + if (finalForce.Length() > 0f) + PhysicsSystem.ApplyForce(shuttleUid, finalForce, body: body); + } - if (!torque.Equals(0f)) - { - PhysicsSystem.ApplyTorque(shuttleUid, torque, body: body); - _thruster.SetAngularThrust(shuttle, true); - } - } + if (MathHelper.CloseTo(angularInput, 0f)) + { + PhysicsSystem.SetSleepingAllowed(shuttleUid, body, true); + + if (brakeInput <= 0f) + _thruster.SetAngularThrust(shuttle, false); } - } + else + { + PhysicsSystem.SetSleepingAllowed(shuttleUid, body, false); + var torque = shuttle.AngularThrust * -angularInput; - // .NET 8 seem to miscompile usage of Vector2.Dot above. This manual outline fixes it pending an upstream fix. - // See PR #24008 - [MethodImpl(MethodImplOptions.NoInlining)] - public static float Vector2Dot(Vector2 value1, Vector2 value2) - { - return Vector2.Dot(value1, value2); - } + // Need to cap the velocity if 1 tick of input brings us over cap so we don't continuously + // edge onto the cap over and over. + var torqueMul = body.InvI * frameTime; - private bool CanPilot(EntityUid shuttleUid) - { - return TryComp(shuttleUid, out var ftl) - && (ftl.State & (FTLState.Starting | FTLState.Travelling | FTLState.Arriving)) != 0x0 - || HasComp(shuttleUid); + torque = Math.Clamp(torque, + (-ShuttleComponent.MaxAngularVelocity - body.AngularVelocity) / torqueMul, + (ShuttleComponent.MaxAngularVelocity - body.AngularVelocity) / torqueMul); + + if (!torque.Equals(0f)) + { + PhysicsSystem.ApplyTorque(shuttleUid, torque, body: body); + _thruster.SetAngularThrust(shuttle, true); + } + } } + } + + // .NET 8 seem to miscompile usage of Vector2.Dot above. This manual outline fixes it pending an upstream fix. + // See PR #24008 + [MethodImpl(MethodImplOptions.NoInlining)] + public static float Vector2Dot(Vector2 value1, Vector2 value2) + { + return Vector2.Dot(value1, value2); + } + private bool CanPilot(EntityUid shuttleUid) + { + return TryComp(shuttleUid, out var ftl) + && (ftl.State & (FTLState.Starting | FTLState.Travelling | FTLState.Arriving)) != 0x0 + || HasComp(shuttleUid); } + } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index c41db21b01eb94..472d56b1d692d7 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -24,492 +24,491 @@ using Robust.Shared.Utility; using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; -namespace Content.Shared.Movement.Systems +namespace Content.Shared.Movement.Systems; + +/// +/// Handles player and NPC mob movement. +/// NPCs are handled server-side only. +/// +public abstract partial class SharedMoverController : VirtualController { + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] protected readonly IGameTiming Timing = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] protected readonly SharedPhysicsSystem Physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly TagSystem _tags = default!; + + protected EntityQuery MoverQuery; + protected EntityQuery MobMoverQuery; + protected EntityQuery RelayTargetQuery; + protected EntityQuery ModifierQuery; + protected EntityQuery PhysicsQuery; + protected EntityQuery RelayQuery; + protected EntityQuery PullableQuery; + protected EntityQuery XformQuery; + protected EntityQuery CanMoveInAirQuery; + protected EntityQuery NoRotateQuery; + protected EntityQuery FootstepModifierQuery; + protected EntityQuery MapGridQuery; + + /// + /// + /// + private float _stopSpeed; + + private bool _relativeMovement; + /// - /// Handles player and NPC mob movement. - /// NPCs are handled server-side only. + /// Cache the mob movement calculation to re-use elsewhere. /// - public abstract partial class SharedMoverController : VirtualController + public Dictionary UsedMobMovement = new(); + + public override void Initialize() { - [Dependency] private readonly IConfigurationManager _configManager = default!; - [Dependency] protected readonly IGameTiming Timing = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedMapSystem _mapSystem = default!; - [Dependency] private readonly SharedGravitySystem _gravity = default!; - [Dependency] protected readonly SharedPhysicsSystem Physics = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly TagSystem _tags = default!; - - protected EntityQuery MoverQuery; - protected EntityQuery MobMoverQuery; - protected EntityQuery RelayTargetQuery; - protected EntityQuery ModifierQuery; - protected EntityQuery PhysicsQuery; - protected EntityQuery RelayQuery; - protected EntityQuery PullableQuery; - protected EntityQuery XformQuery; - protected EntityQuery CanMoveInAirQuery; - protected EntityQuery NoRotateQuery; - protected EntityQuery FootstepModifierQuery; - protected EntityQuery MapGridQuery; - - /// - /// - /// - private float _stopSpeed; - - private bool _relativeMovement; - - /// - /// Cache the mob movement calculation to re-use elsewhere. - /// - public Dictionary UsedMobMovement = new(); - - public override void Initialize() - { - base.Initialize(); - - MoverQuery = GetEntityQuery(); - MobMoverQuery = GetEntityQuery(); - ModifierQuery = GetEntityQuery(); - RelayTargetQuery = GetEntityQuery(); - PhysicsQuery = GetEntityQuery(); - RelayQuery = GetEntityQuery(); - PullableQuery = GetEntityQuery(); - XformQuery = GetEntityQuery(); - NoRotateQuery = GetEntityQuery(); - CanMoveInAirQuery = GetEntityQuery(); - FootstepModifierQuery = GetEntityQuery(); - MapGridQuery = GetEntityQuery(); - - InitializeInput(); - InitializeRelay(); - Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true); - Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true); - UpdatesBefore.Add(typeof(TileFrictionController)); - } + base.Initialize(); + + MoverQuery = GetEntityQuery(); + MobMoverQuery = GetEntityQuery(); + ModifierQuery = GetEntityQuery(); + RelayTargetQuery = GetEntityQuery(); + PhysicsQuery = GetEntityQuery(); + RelayQuery = GetEntityQuery(); + PullableQuery = GetEntityQuery(); + XformQuery = GetEntityQuery(); + NoRotateQuery = GetEntityQuery(); + CanMoveInAirQuery = GetEntityQuery(); + FootstepModifierQuery = GetEntityQuery(); + MapGridQuery = GetEntityQuery(); + + InitializeInput(); + InitializeRelay(); + Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true); + Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true); + UpdatesBefore.Add(typeof(TileFrictionController)); + } - public override void Shutdown() - { - base.Shutdown(); - ShutdownInput(); - } + public override void Shutdown() + { + base.Shutdown(); + ShutdownInput(); + } - public override void UpdateAfterSolve(bool prediction, float frameTime) - { - base.UpdateAfterSolve(prediction, frameTime); - UsedMobMovement.Clear(); - } + public override void UpdateAfterSolve(bool prediction, float frameTime) + { + base.UpdateAfterSolve(prediction, frameTime); + UsedMobMovement.Clear(); + } - /// - /// Movement while considering actionblockers, weightlessness, etc. - /// - protected void HandleMobMovement( - EntityUid uid, - InputMoverComponent mover, - EntityUid physicsUid, - PhysicsComponent physicsComponent, - TransformComponent xform, - float frameTime) + /// + /// Movement while considering actionblockers, weightlessness, etc. + /// + protected void HandleMobMovement( + EntityUid uid, + InputMoverComponent mover, + EntityUid physicsUid, + PhysicsComponent physicsComponent, + TransformComponent xform, + float frameTime) + { + var canMove = mover.CanMove; + if (RelayTargetQuery.TryGetComponent(uid, out var relayTarget)) { - var canMove = mover.CanMove; - if (RelayTargetQuery.TryGetComponent(uid, out var relayTarget)) + if (_mobState.IsIncapacitated(relayTarget.Source) || + TryComp(relayTarget.Source, out _) || + !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover)) { - if (_mobState.IsIncapacitated(relayTarget.Source) || - TryComp(relayTarget.Source, out _) || - !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover)) - { - canMove = false; - } - else - { - mover.RelativeEntity = relayedMover.RelativeEntity; - mover.RelativeRotation = relayedMover.RelativeRotation; - mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation; - } + canMove = false; } - - // Update relative movement - if (mover.LerpTarget < Timing.CurTime) + else { - if (TryUpdateRelative(mover, xform)) - { - Dirty(uid, mover); - } + mover.RelativeEntity = relayedMover.RelativeEntity; + mover.RelativeRotation = relayedMover.RelativeRotation; + mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation; } + } - LerpRotation(uid, mover, frameTime); - - if (!canMove - || physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid) - || PullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled) + // Update relative movement + if (mover.LerpTarget < Timing.CurTime) + { + if (TryUpdateRelative(mover, xform)) { - UsedMobMovement[uid] = false; - return; + Dirty(uid, mover); } + } + LerpRotation(uid, mover, frameTime); - UsedMobMovement[uid] = true; - // Specifically don't use mover.Owner because that may be different to the actual physics body being moved. - var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform); - var (walkDir, sprintDir) = GetVelocityInput(mover); - var touching = false; - - // Handle wall-pushes. - if (weightless) - { - if (xform.GridUid != null) - touching = true; + if (!canMove + || physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid) + || PullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled) + { + UsedMobMovement[uid] = false; + return; + } - if (!touching) - { - var ev = new CanWeightlessMoveEvent(uid); - RaiseLocalEvent(uid, ref ev, true); - // No gravity: is our entity touching anything? - touching = ev.CanMove; - if (!touching && TryComp(uid, out var mobMover)) - touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent); - } - } + UsedMobMovement[uid] = true; + // Specifically don't use mover.Owner because that may be different to the actual physics body being moved. + var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform); + var (walkDir, sprintDir) = GetVelocityInput(mover); + var touching = false; - // Get current tile def for things like speed/friction mods - ContentTileDefinition? tileDef = null; + // Handle wall-pushes. + if (weightless) + { + if (xform.GridUid != null) + touching = true; - // Don't bother getting the tiledef here if we're weightless or in-air - // since no tile-based modifiers should be applying in that situation - if (MapGridQuery.TryComp(xform.GridUid, out var gridComp) - && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile) - && !(weightless || physicsComponent.BodyStatus == BodyStatus.InAir)) + if (!touching) { - tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId]; + var ev = new CanWeightlessMoveEvent(uid); + RaiseLocalEvent(uid, ref ev, true); + // No gravity: is our entity touching anything? + touching = ev.CanMove; + + if (!touching && TryComp(uid, out var mobMover)) + touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent); } + } - // Regular movement. - // Target velocity. - // This is relative to the map / grid we're on. - var moveSpeedComponent = ModifierQuery.CompOrNull(uid); + // Get current tile def for things like speed/friction mods + ContentTileDefinition? tileDef = null; - var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; - var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; + // Don't bother getting the tiledef here if we're weightless or in-air + // since no tile-based modifiers should be applying in that situation + if (MapGridQuery.TryComp(xform.GridUid, out var gridComp) + && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile) + && !(weightless || physicsComponent.BodyStatus == BodyStatus.InAir)) + { + tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId]; + } - var total = walkDir * walkSpeed + sprintDir * sprintSpeed; + // Regular movement. + // Target velocity. + // This is relative to the map / grid we're on. + var moveSpeedComponent = ModifierQuery.CompOrNull(uid); - var parentRotation = GetParentGridAngle(mover); - var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total; + var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; + var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; - DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), worldTotal.Length())); + var total = walkDir * walkSpeed + sprintDir * sprintSpeed; - var velocity = physicsComponent.LinearVelocity; - float friction; - float weightlessModifier; - float accel; + var parentRotation = GetParentGridAngle(mover); + var worldTotal = _relativeMovement ? parentRotation.RotateVec(total) : total; - if (weightless) - { - if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid)) - friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction; - else if (worldTotal != Vector2.Zero && touching) - friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction; - else - friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput; + DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), worldTotal.Length())); + + var velocity = physicsComponent.LinearVelocity; + float friction; + float weightlessModifier; + float accel; + + if (weightless) + { + if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid)) + friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction; + else if (worldTotal != Vector2.Zero && touching) + friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction; + else + friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput; - weightlessModifier = moveSpeedComponent?.WeightlessModifier ?? MovementSpeedModifierComponent.DefaultWeightlessModifier; - accel = moveSpeedComponent?.WeightlessAcceleration ?? MovementSpeedModifierComponent.DefaultWeightlessAcceleration; + weightlessModifier = moveSpeedComponent?.WeightlessModifier ?? MovementSpeedModifierComponent.DefaultWeightlessModifier; + accel = moveSpeedComponent?.WeightlessAcceleration ?? MovementSpeedModifierComponent.DefaultWeightlessAcceleration; + } + else + { + if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null) + { + friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction; } else { - if (worldTotal != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null) - { - friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction; - } - else - { - friction = tileDef?.MobFrictionNoInput ?? moveSpeedComponent.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput; - } - - weightlessModifier = 1f; - accel = tileDef?.MobAcceleration ?? moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration; + friction = tileDef?.MobFrictionNoInput ?? moveSpeedComponent.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput; } - var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed; - Friction(minimumFrictionSpeed, frameTime, friction, ref velocity); + weightlessModifier = 1f; + accel = tileDef?.MobAcceleration ?? moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration; + } - if (worldTotal != Vector2.Zero) + var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed; + Friction(minimumFrictionSpeed, frameTime, friction, ref velocity); + + if (worldTotal != Vector2.Zero) + { + if (!NoRotateQuery.HasComponent(uid)) + { + // TODO apparently this results in a duplicate move event because "This should have its event run during + // island solver"??. So maybe SetRotation needs an argument to avoid raising an event? + var worldRot = _transform.GetWorldRotation(xform); + _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot); + } + + if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) && + TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef)) { - if (!NoRotateQuery.HasComponent(uid)) + var soundModifier = mover.Sprinting ? 3.5f : 1.5f; + + var audioParams = sound.Params + .WithVolume(sound.Params.Volume + soundModifier) + .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation); + + // If we're a relay target then predict the sound for all relays. + if (relayTarget != null) { - // TODO apparently this results in a duplicate move event because "This should have its event run during - // island solver"??. So maybe SetRotation needs an argument to avoid raising an event? - var worldRot = _transform.GetWorldRotation(xform); - _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot); + _audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams); } - - if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) && - TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef)) + else { - var soundModifier = mover.Sprinting ? 3.5f : 1.5f; - - var audioParams = sound.Params - .WithVolume(sound.Params.Volume + soundModifier) - .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation); - - // If we're a relay target then predict the sound for all relays. - if (relayTarget != null) - { - _audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams); - } - else - { - _audio.PlayPredicted(sound, uid, uid, audioParams); - } + _audio.PlayPredicted(sound, uid, uid, audioParams); } } + } - worldTotal *= weightlessModifier; + worldTotal *= weightlessModifier; - if (!weightless || touching) - Accelerate(ref velocity, in worldTotal, accel, frameTime); + if (!weightless || touching) + Accelerate(ref velocity, in worldTotal, accel, frameTime); - PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent); + PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent); - // Ensures that players do not spiiiiiiin - PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent); - } + // Ensures that players do not spiiiiiiin + PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent); + } - public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime) + public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime) + { + var angleDiff = Angle.ShortestDistance(mover.RelativeRotation, mover.TargetRelativeRotation); + + // if we've just traversed then lerp to our target rotation. + if (!angleDiff.EqualsApprox(Angle.Zero, 0.001)) { - var angleDiff = Angle.ShortestDistance(mover.RelativeRotation, mover.TargetRelativeRotation); + var adjustment = angleDiff * 5f * frameTime; + var minAdjustment = 0.01 * frameTime; - // if we've just traversed then lerp to our target rotation. - if (!angleDiff.EqualsApprox(Angle.Zero, 0.001)) + if (angleDiff < 0) { - var adjustment = angleDiff * 5f * frameTime; - var minAdjustment = 0.01 * frameTime; - - if (angleDiff < 0) - { - adjustment = Math.Min(adjustment, -minAdjustment); - adjustment = Math.Clamp(adjustment, angleDiff, -angleDiff); - } - else - { - adjustment = Math.Max(adjustment, minAdjustment); - adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff); - } - - mover.RelativeRotation += adjustment; - mover.RelativeRotation.FlipPositive(); - Dirty(uid, mover); + adjustment = Math.Min(adjustment, -minAdjustment); + adjustment = Math.Clamp(adjustment, angleDiff, -angleDiff); } - else if (!angleDiff.Equals(Angle.Zero)) + else { - mover.TargetRelativeRotation.FlipPositive(); - mover.RelativeRotation = mover.TargetRelativeRotation; - Dirty(uid, mover); + adjustment = Math.Max(adjustment, minAdjustment); + adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff); } - } - private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) + mover.RelativeRotation += adjustment; + mover.RelativeRotation.FlipPositive(); + Dirty(uid, mover); + } + else if (!angleDiff.Equals(Angle.Zero)) { - var speed = velocity.Length(); + mover.TargetRelativeRotation.FlipPositive(); + mover.RelativeRotation = mover.TargetRelativeRotation; + Dirty(uid, mover); + } + } - if (speed < minimumFrictionSpeed) - return; + private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) + { + var speed = velocity.Length(); - var drop = 0f; + if (speed < minimumFrictionSpeed) + return; - var control = MathF.Max(_stopSpeed, speed); - drop += control * friction * frameTime; + var drop = 0f; - var newSpeed = MathF.Max(0f, speed - drop); + var control = MathF.Max(_stopSpeed, speed); + drop += control * friction * frameTime; - if (newSpeed.Equals(speed)) - return; + var newSpeed = MathF.Max(0f, speed - drop); - newSpeed /= speed; - velocity *= newSpeed; - } + if (newSpeed.Equals(speed)) + return; - 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(); + newSpeed /= speed; + velocity *= newSpeed; + } - var currentSpeed = Vector2.Dot(currentVelocity, wishDir); - var addSpeed = wishSpeed - currentSpeed; + 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(); - if (addSpeed <= 0f) - return; + var currentSpeed = Vector2.Dot(currentVelocity, wishDir); + var addSpeed = wishSpeed - currentSpeed; - var accelSpeed = accel * frameTime * wishSpeed; - accelSpeed = MathF.Min(accelSpeed, addSpeed); + if (addSpeed <= 0f) + return; - currentVelocity += wishDir * accelSpeed; - } + var accelSpeed = accel * frameTime * wishSpeed; + accelSpeed = MathF.Min(accelSpeed, addSpeed); - public bool UseMobMovement(EntityUid uid) - { - return UsedMobMovement.TryGetValue(uid, out var used) && used; - } + currentVelocity += wishDir * accelSpeed; + } - /// - /// Used for weightlessness to determine if we are near a wall. - /// - private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, EntityUid physicsUid, PhysicsComponent collider) - { - var enlargedAABB = _lookup.GetWorldAABB(physicsUid, transform).Enlarged(mover.GrabRangeVV); + public bool UseMobMovement(EntityUid uid) + { + return UsedMobMovement.TryGetValue(uid, out var used) && used; + } - foreach (var otherCollider in broadPhaseSystem.GetCollidingEntities(transform.MapID, enlargedAABB)) - { - if (otherCollider == collider) - continue; // Don't try to push off of yourself! - - // Only allow pushing off of anchored things that have collision. - if (otherCollider.BodyType != BodyType.Static || - !otherCollider.CanCollide || - ((collider.CollisionMask & otherCollider.CollisionLayer) == 0 && - (otherCollider.CollisionMask & collider.CollisionLayer) == 0) || - (TryComp(otherCollider.Owner, out PullableComponent? pullable) && pullable.BeingPulled)) - { - continue; - } + /// + /// Used for weightlessness to determine if we are near a wall. + /// + private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, EntityUid physicsUid, PhysicsComponent collider) + { + var enlargedAABB = _lookup.GetWorldAABB(physicsUid, transform).Enlarged(mover.GrabRangeVV); - return true; + foreach (var otherCollider in broadPhaseSystem.GetCollidingEntities(transform.MapID, enlargedAABB)) + { + if (otherCollider == collider) + continue; // Don't try to push off of yourself! + + // Only allow pushing off of anchored things that have collision. + if (otherCollider.BodyType != BodyType.Static || + !otherCollider.CanCollide || + ((collider.CollisionMask & otherCollider.CollisionLayer) == 0 && + (otherCollider.CollisionMask & collider.CollisionLayer) == 0) || + (TryComp(otherCollider.Owner, out PullableComponent? pullable) && pullable.BeingPulled)) + { + continue; } - return false; + return true; } - protected abstract bool CanSound(); + return false; + } + + protected abstract bool CanSound(); - private bool TryGetSound( - bool weightless, - EntityUid uid, - InputMoverComponent mover, - MobMoverComponent mobMover, - TransformComponent xform, - [NotNullWhen(true)] out SoundSpecifier? sound, - ContentTileDefinition? tileDef = null) - { - sound = null; + private bool TryGetSound( + bool weightless, + EntityUid uid, + InputMoverComponent mover, + MobMoverComponent mobMover, + TransformComponent xform, + [NotNullWhen(true)] out SoundSpecifier? sound, + ContentTileDefinition? tileDef = null) + { + sound = null; - if (!CanSound() || !_tags.HasTag(uid, "FootstepSound")) - return false; + if (!CanSound() || !_tags.HasTag(uid, "FootstepSound")) + return false; - var coordinates = xform.Coordinates; - var distanceNeeded = mover.Sprinting - ? mobMover.StepSoundMoveDistanceRunning - : mobMover.StepSoundMoveDistanceWalking; + var coordinates = xform.Coordinates; + var distanceNeeded = mover.Sprinting + ? mobMover.StepSoundMoveDistanceRunning + : mobMover.StepSoundMoveDistanceWalking; - // Handle footsteps. - if (!weightless) + // Handle footsteps. + if (!weightless) + { + // Can happen when teleporting between grids. + if (!coordinates.TryDistance(EntityManager, mobMover.LastPosition, out var distance) || + distance > distanceNeeded) { - // Can happen when teleporting between grids. - if (!coordinates.TryDistance(EntityManager, mobMover.LastPosition, out var distance) || - distance > distanceNeeded) - { - mobMover.StepSoundDistance = distanceNeeded; - } - else - { - mobMover.StepSoundDistance += distance; - } + mobMover.StepSoundDistance = distanceNeeded; } else { - // In space no one can hear you squeak - return false; + mobMover.StepSoundDistance += distance; } + } + else + { + // In space no one can hear you squeak + return false; + } - mobMover.LastPosition = coordinates; + mobMover.LastPosition = coordinates; - if (mobMover.StepSoundDistance < distanceNeeded) - return false; + if (mobMover.StepSoundDistance < distanceNeeded) + return false; - mobMover.StepSoundDistance -= distanceNeeded; + mobMover.StepSoundDistance -= distanceNeeded; - if (FootstepModifierQuery.TryComp(uid, out var moverModifier)) - { - sound = moverModifier.FootstepSoundCollection; - return true; - } + if (FootstepModifierQuery.TryComp(uid, out var moverModifier)) + { + sound = moverModifier.FootstepSoundCollection; + return true; + } - if (_inventory.TryGetSlotEntity(uid, "shoes", out var shoes) && - FootstepModifierQuery.TryComp(shoes, out var modifier)) + if (_inventory.TryGetSlotEntity(uid, "shoes", out var shoes) && + FootstepModifierQuery.TryComp(shoes, out var modifier)) + { + sound = modifier.FootstepSoundCollection; + return true; + } + + return TryGetFootstepSound(uid, xform, shoes != null, out sound, tileDef: tileDef); + } + + private bool TryGetFootstepSound( + EntityUid uid, + TransformComponent xform, + bool haveShoes, + [NotNullWhen(true)] out SoundSpecifier? sound, + ContentTileDefinition? tileDef = null) + { + sound = null; + + // Fallback to the map? + if (!MapGridQuery.TryComp(xform.GridUid, out var grid)) + { + if (FootstepModifierQuery.TryComp(xform.MapUid, out var modifier)) { sound = modifier.FootstepSoundCollection; return true; } - return TryGetFootstepSound(uid, xform, shoes != null, out sound, tileDef: tileDef); + return false; } - private bool TryGetFootstepSound( - EntityUid uid, - TransformComponent xform, - bool haveShoes, - [NotNullWhen(true)] out SoundSpecifier? sound, - ContentTileDefinition? tileDef = null) - { - sound = null; + var position = grid.LocalToTile(xform.Coordinates); + var soundEv = new GetFootstepSoundEvent(uid); - // Fallback to the map? - if (!MapGridQuery.TryComp(xform.GridUid, out var grid)) - { - if (FootstepModifierQuery.TryComp(xform.MapUid, out var modifier)) - { - sound = modifier.FootstepSoundCollection; - return true; - } - - return false; - } - - var position = grid.LocalToTile(xform.Coordinates); - var soundEv = new GetFootstepSoundEvent(uid); + // If the coordinates have a FootstepModifier component + // i.e. component that emit sound on footsteps emit that sound + var anchored = grid.GetAnchoredEntitiesEnumerator(position); - // If the coordinates have a FootstepModifier component - // i.e. component that emit sound on footsteps emit that sound - var anchored = grid.GetAnchoredEntitiesEnumerator(position); + while (anchored.MoveNext(out var maybeFootstep)) + { + RaiseLocalEvent(maybeFootstep.Value, ref soundEv); - while (anchored.MoveNext(out var maybeFootstep)) + if (soundEv.Sound != null) { - RaiseLocalEvent(maybeFootstep.Value, ref soundEv); - - if (soundEv.Sound != null) - { - sound = soundEv.Sound; - return true; - } - - if (FootstepModifierQuery.TryComp(maybeFootstep, out var footstep)) - { - sound = footstep.FootstepSoundCollection; - return true; - } + sound = soundEv.Sound; + return true; } - // Walking on a tile. - // Tile def might have been passed in already from previous methods, so use that - // if we have it - if (tileDef == null && grid.TryGetTileRef(position, out var tileRef)) + if (FootstepModifierQuery.TryComp(maybeFootstep, out var footstep)) { - tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId]; + sound = footstep.FootstepSoundCollection; + return true; } + } - if (tileDef == null) - return false; - - sound = haveShoes ? tileDef.FootstepSounds : tileDef.BarestepSounds; - return sound != null; + // Walking on a tile. + // Tile def might have been passed in already from previous methods, so use that + // if we have it + if (tileDef == null && grid.TryGetTileRef(position, out var tileRef)) + { + tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId]; } + + if (tileDef == null) + return false; + + sound = haveShoes ? tileDef.FootstepSounds : tileDef.BarestepSounds; + return sound != null; } }