diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 09dba9ae5b..7cf1e72255 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -1,14 +1,11 @@ -using Content.Shared.Inventory; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Pulling.Components; using Robust.Client.GameObjects; using Robust.Client.Player; -using Robust.Shared.Containers; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Client.Physics.Controllers { @@ -137,30 +134,8 @@ namespace Content.Client.Physics.Controllers } } - var mobQuery = GetEntityQuery(); - var inventoryQuery = GetEntityQuery(); - var containerQuery = GetEntityQuery(); - var footQuery = GetEntityQuery(); - DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner)); - // Server-side should just be handled on its own so we'll just do this shizznit - HandleMobMovement(mover, body, xformMover, frameTime, xformQuery, mobQuery, inventoryQuery, containerQuery, footQuery, out var dirtyMover, out var linearVelocity, out var sound, out var audio); - - MetaDataComponent? metadata = null; - - if (dirtyMover) - { - Dirty(mover, metadata); - } - - if (linearVelocity != null) - { - PhysicsSystem.SetLinearVelocity(body, linearVelocity.Value, false); - PhysicsSystem.SetAngularVelocity(body, 0f, false); - Dirty(body, metadata); - } - - Audio.PlayPredicted(sound, mover.Owner, mover.Owner, audio); + HandleMobMovement(mover, body, xformMover, frameTime, xformQuery); } protected override bool CanSound() diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index ce331cc37a..0793b07e77 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -1,28 +1,20 @@ -using System.Buffers; -using System.Threading.Tasks; using Content.Server.Cargo.Components; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; -using Content.Shared.Inventory; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Physics.Components; using Robust.Shared.Player; -using Robust.Shared.Threading; -using Robust.Shared.Utility; namespace Content.Server.Physics.Controllers { public sealed class MoverController : SharedMoverController { [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IParallelManager _parallel = default!; [Dependency] private readonly ThrusterSystem _thruster = default!; private Dictionary> _shuttlePilots = new(); @@ -72,12 +64,7 @@ namespace Content.Server.Physics.Controllers var xformQuery = GetEntityQuery(); var moverQuery = GetEntityQuery(); - var movers = AllEntityQuery(); - var totalCount = EntityManager.Count(); - var moveInput = ArrayPool<(InputMoverComponent Mover, TransformComponent Transform, PhysicsComponent Physics)>.Shared.Rent(totalCount); - var count = 0; - - while (movers.MoveNext(out var mover)) + foreach (var mover in EntityQuery(true)) { if (relayQuery.TryGetComponent(mover.Owner, out var relayed) && relayed.RelayEntity != null) { @@ -96,11 +83,13 @@ namespace Content.Server.Physics.Controllers continue; } - PhysicsComponent? body; + PhysicsComponent? body = null; + TransformComponent? xformMover = xform; + if (mover.ToParent && relayQuery.HasComponent(xform.ParentUid)) { if (!bodyQuery.TryGetComponent(xform.ParentUid, out body) || - !xformQuery.HasComponent(xform.ParentUid)) + !TryComp(xform.ParentUid, out xformMover)) { continue; } @@ -110,64 +99,9 @@ namespace Content.Server.Physics.Controllers continue; } - DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner)); - - // To avoid threading issues on adding dictionary entries later. - UsedMobMovement[mover.Owner] = false; - moveInput[count++] = (mover, xform, body); + HandleMobMovement(mover, body, xformMover, frameTime, xformQuery); } - var moveResults = ArrayPool<(bool DirtyMover, Vector2? LinearVelocity, SoundSpecifier? sound, AudioParams audio)>.Shared.Rent(count); - var mobQuery = GetEntityQuery(); - var inventoryQuery = GetEntityQuery(); - var containerQuery = GetEntityQuery(); - var footQuery = GetEntityQuery(); - - var options = new ParallelOptions() - { - MaxDegreeOfParallelism = _parallel.ParallelProcessCount, - }; - - Parallel.For(0, count, options, i => - { - var (mover, xform, body) = moveInput[i]; - HandleMobMovement(mover, body, xform, frameTime, xformQuery, mobQuery, inventoryQuery, containerQuery, footQuery, out var dirtyMover, out var linearVelocity, out var sound, out var audio); - moveResults[i] = (dirtyMover, linearVelocity, sound, audio); - }); - - var metaQuery = GetEntityQuery(); - - for (var i = 0; i < count; i++) - { - var results = moveResults[i]; - var input = moveInput[i]; - MetaDataComponent? metadata = null; - - // Calling dirty isn't thread-safe sadly. - if (results.DirtyMover) - { - metadata ??= metaQuery.GetComponent(input.Mover.Owner); - Dirty(input.Mover, metadata); - } - - if (results.LinearVelocity != null) - { - metadata ??= metaQuery.GetComponent(input.Physics.Owner); - PhysicsSystem.SetLinearVelocity(input.Physics, results.LinearVelocity.Value, false); - PhysicsSystem.SetAngularVelocity(input.Physics, 0f, false); - Dirty(input.Physics, metadata); - } - - if (results.sound != null) - { - Audio.PlayPredicted(results.sound, input.Mover.Owner, input.Mover.Owner, results.audio); - } - - moveInput[i] = default; - } - - ArrayPool<(bool DirtyMover, Vector2? LinearVelocity, SoundSpecifier? Sound, AudioParams Audio)>.Shared.Return(moveResults); - ArrayPool<(InputMoverComponent, TransformComponent, PhysicsComponent)>.Shared.Return(moveInput); HandleShuttleMovement(frameTime); } diff --git a/Content.Shared/Movement/Components/InputMoverComponent.cs b/Content.Shared/Movement/Components/InputMoverComponent.cs index 0b87e4311d..49a82df1d3 100644 --- a/Content.Shared/Movement/Components/InputMoverComponent.cs +++ b/Content.Shared/Movement/Components/InputMoverComponent.cs @@ -1,6 +1,5 @@ using Content.Shared.Movement.Systems; using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Timing; namespace Content.Shared.Movement.Components @@ -62,8 +61,8 @@ namespace Content.Shared.Movement.Components /// /// If we traverse on / off a grid then set a timer to update our relative inputs. /// - [ViewVariables(VVAccess.ReadWrite), DataField("lerpTarget", customTypeSerializer: typeof(TimeOffsetSerializer))] - public TimeSpan LerpTarget; + [ViewVariables(VVAccess.ReadWrite)] + public float LerpAccumulator; public const float LerpTime = 1.0f; diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 2ccdb30ff9..27286dc701 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -80,7 +80,7 @@ namespace Content.Shared.Movement.Systems component.RelativeRotation = state.RelativeRotation; component.TargetRelativeRotation = state.TargetRelativeRotation; component.RelativeEntity = state.RelativeEntity; - component.LerpTarget = state.LerpTarget; + component.LerpAccumulator = state.LerpAccumulator; } private void OnInputGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args) @@ -91,7 +91,7 @@ namespace Content.Shared.Movement.Systems component.RelativeRotation, component.TargetRelativeRotation, component.RelativeEntity, - component.LerpTarget); + component.LerpAccumulator); } private void ShutdownInput() @@ -118,73 +118,13 @@ namespace Content.Shared.Movement.Systems public void ResetCamera(EntityUid uid) { - if (CameraRotationLocked || - !TryComp(uid, out var mover)) - { - return; - } - - // If we updated parent then cancel the accumulator and force it now. - var xformQuery = GetEntityQuery(); - - if (!TryUpdateRelative(mover, xformQuery.GetComponent(uid), xformQuery) && mover.TargetRelativeRotation.Equals(Angle.Zero)) + if (CameraRotationLocked || !TryComp(uid, out var mover) || mover.TargetRelativeRotation.Equals(Angle.Zero)) return; - mover.LerpTarget = TimeSpan.Zero; mover.TargetRelativeRotation = Angle.Zero; Dirty(mover); } - private bool TryUpdateRelative(InputMoverComponent mover, TransformComponent xform, EntityQuery xformQuery) - { - var relative = xform.GridUid; - relative ??= xform.MapUid; - - // So essentially what we want: - // 1. If we go from grid to map then preserve our rotation and continue as usual - // 2. If we go from grid -> grid then (after lerp time) snap to nearest cardinal (probably imperceptible) - // 3. If we go from map -> grid then (after lerp time) snap to nearest cardinal - - if (mover.RelativeEntity.Equals(relative)) - return false; - - // Okay need to get our old relative rotation with respect to our new relative rotation - // e.g. if we were right side up on our current grid need to get what that is on our new grid. - var currentRotation = Angle.Zero; - var targetRotation = Angle.Zero; - - // Get our current relative rotation - if (xformQuery.TryGetComponent(mover.RelativeEntity, out var oldRelativeXform)) - { - currentRotation = _transform.GetWorldRotation(oldRelativeXform, xformQuery) + mover.RelativeRotation; - } - - if (xformQuery.TryGetComponent(relative, out var relativeXform)) - { - // This is our current rotation relative to our new parent. - mover.RelativeRotation = (currentRotation - _transform.GetWorldRotation(relativeXform, xformQuery)).FlipPositive(); - } - - // If we went from grid -> map we'll preserve our worldrotation - if (relative != null && _mapManager.IsMap(relative.Value)) - { - targetRotation = currentRotation.FlipPositive().Reduced(); - } - // If we went from grid -> grid OR grid -> map then snap the target to cardinal and lerp there. - // OR just rotate to zero (depending on cvar) - else if (relative != null && _mapManager.IsGrid(relative.Value)) - { - if (CameraRotationLocked) - targetRotation = Angle.Zero; - else - targetRotation = mover.RelativeRotation.GetCardinalDir().ToAngle().Reduced(); - } - - mover.RelativeEntity = relative; - mover.TargetRelativeRotation = targetRotation; - return true; - } - public Angle GetParentGridAngle(InputMoverComponent mover, EntityQuery xformQuery) { var rotation = mover.RelativeRotation; @@ -221,16 +161,16 @@ namespace Content.Shared.Movement.Systems // If we go on a grid and back off then just reset the accumulator. if (relative == component.RelativeEntity) { - if (component.LerpTarget >= Timing.CurTime) + if (component.LerpAccumulator != 0f) { - component.LerpTarget = TimeSpan.Zero; + component.LerpAccumulator = 0f; Dirty(component); } return; } - component.LerpTarget = TimeSpan.FromSeconds(InputMoverComponent.LerpTime) + Timing.CurTime; + component.LerpAccumulator = InputMoverComponent.LerpTime; Dirty(component); } @@ -559,16 +499,16 @@ namespace Content.Shared.Movement.Systems /// public Angle TargetRelativeRotation; public EntityUid? RelativeEntity; - public TimeSpan LerpTarget; + public float LerpAccumulator = 0f; - public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, TimeSpan lerpTarget) + public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, float lerpAccumulator) { Buttons = buttons; CanMove = canMove; RelativeRotation = relativeRotation; TargetRelativeRotation = targetRelativeRotation; RelativeEntity = relativeEntity; - LerpTarget = lerpTarget; + LerpAccumulator = lerpAccumulator; } } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index d79f1a81f4..10ab0e165f 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -28,17 +28,17 @@ namespace Content.Shared.Movement.Systems /// public abstract partial class SharedMoverController : VirtualController { - [Dependency] private readonly IConfigurationManager _configManager = default!; + [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 InventorySystem _inventory = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly SharedGravitySystem _gravity = default!; - [Dependency] private readonly SharedMobStateSystem _mobState = default!; - [Dependency] protected readonly SharedAudioSystem Audio = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly TagSystem _tags = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly SharedMobStateSystem _mobState = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly TagSystem _tags = default!; private const float StepSoundMoveDistanceRunning = 2; private const float StepSoundMoveDistanceWalking = 1.5f; @@ -96,35 +96,69 @@ namespace Content.Shared.Movement.Systems /// /// Movement while considering actionblockers, weightlessness, etc. /// - /// - /// Yes this signature is massive but this is also called a lot. - /// protected void HandleMobMovement( InputMoverComponent mover, PhysicsComponent physicsComponent, TransformComponent xform, float frameTime, - EntityQuery xformQuery, - EntityQuery mobQuery, - EntityQuery inventoryQuery, - EntityQuery containerQuery, - EntityQuery footQuery, - out bool dirtyMover, - out Vector2? linearVelocity, - out SoundSpecifier? sound, - out AudioParams audio) + EntityQuery xformQuery) { - dirtyMover = false; - linearVelocity = Vector2.Zero; - sound = null; - audio = default; + DebugTools.Assert(!UsedMobMovement.ContainsKey(mover.Owner)); // Update relative movement - if (mover.LerpTarget < Timing.CurTime) + if (mover.LerpAccumulator > 0f) { - if (TryUpdateRelative(mover, xform, xformQuery)) + Dirty(mover); + mover.LerpAccumulator -= frameTime; + + if (mover.LerpAccumulator <= 0f) { - dirtyMover = true; + mover.LerpAccumulator = 0f; + var relative = xform.GridUid; + relative ??= xform.MapUid; + + // So essentially what we want: + // 1. If we go from grid to map then preserve our rotation and continue as usual + // 2. If we go from grid -> grid then (after lerp time) snap to nearest cardinal (probably imperceptible) + // 3. If we go from map -> grid then (after lerp time) snap to nearest cardinal + + if (!mover.RelativeEntity.Equals(relative)) + { + // Okay need to get our old relative rotation with respect to our new relative rotation + // e.g. if we were right side up on our current grid need to get what that is on our new grid. + var currentRotation = Angle.Zero; + var targetRotation = Angle.Zero; + + // Get our current relative rotation + if (xformQuery.TryGetComponent(mover.RelativeEntity, out var oldRelativeXform)) + { + currentRotation = oldRelativeXform.WorldRotation + mover.RelativeRotation; + } + + if (xformQuery.TryGetComponent(relative, out var relativeXform)) + { + // This is our current rotation relative to our new parent. + mover.RelativeRotation = (currentRotation - relativeXform.WorldRotation).FlipPositive(); + } + + // If we went from grid -> map we'll preserve our worldrotation + if (relative != null && _mapManager.IsMap(relative.Value)) + { + targetRotation = currentRotation.FlipPositive().Reduced(); + } + // If we went from grid -> grid OR grid -> map then snap the target to cardinal and lerp there. + // OR just rotate to zero (depending on cvar) + else if (relative != null && _mapManager.IsGrid(relative.Value)) + { + if (CameraRotationLocked) + targetRotation = Angle.Zero; + else + targetRotation = mover.RelativeRotation.GetCardinalDir().ToAngle().Reduced(); + } + + mover.RelativeEntity = relative; + mover.TargetRelativeRotation = targetRotation; + } } } @@ -149,13 +183,13 @@ namespace Content.Shared.Movement.Systems mover.RelativeRotation += adjustment; mover.RelativeRotation.FlipPositive(); - dirtyMover = true; + Dirty(mover); } else if (!angleDiff.Equals(Angle.Zero)) { mover.TargetRelativeRotation.FlipPositive(); mover.RelativeRotation = mover.TargetRelativeRotation; - dirtyMover = true; + Dirty(mover); } if (!UseMobMovement(mover, physicsComponent)) @@ -183,7 +217,7 @@ namespace Content.Shared.Movement.Systems // No gravity: is our entity touching anything? touching = ev.CanMove; - if (!touching && mobQuery.TryGetComponent(xform.Owner, out var mobMover)) + if (!touching && TryComp(xform.Owner, out var mobMover)) touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent); } } @@ -240,20 +274,19 @@ namespace Content.Shared.Movement.Systems { // This should have its event run during island solver soooo xform.DeferUpdates = true; - _transform.SetWorldRotation(xform, worldTotal.ToWorldAngle(), xformQuery); + xform.WorldRotation = worldTotal.ToWorldAngle(); xform.DeferUpdates = false; if (!weightless && TryComp(mover.Owner, out var mobMover) && - TryGetSound(weightless, mover, mobMover, xform, inventoryQuery, containerQuery, footQuery, out var soundSpec)) + TryGetSound(weightless, mover, mobMover, xform, out var sound)) { var soundModifier = mover.Sprinting ? 1.0f : FootstepWalkingAddedVolumeMultiplier; - var audioParams = soundSpec.Params + var audioParams = sound.Params .WithVolume(FootstepVolume * soundModifier) - .WithVariation(soundSpec.Params.Variation ?? FootstepVariation); + .WithVariation(sound.Params.Variation ?? FootstepVariation); - sound = soundSpec; - audio = audioParams; + _audio.PlayPredicted(sound, mover.Owner, mover.Owner, audioParams); } } @@ -262,13 +295,10 @@ namespace Content.Shared.Movement.Systems if (!weightless || touching) Accelerate(ref velocity, in worldTotal, accel, frameTime); - if (physicsComponent.LinearVelocity.EqualsApprox(velocity, 0.0001f) && - MathHelper.CloseTo(physicsComponent.AngularVelocity, 0f)) - { - return; - } + PhysicsSystem.SetLinearVelocity(physicsComponent, velocity); - linearVelocity = velocity; + // Ensures that players do not spiiiiiiin + PhysicsSystem.SetAngularVelocity(physicsComponent, 0); } private void Friction(float minimumFrictionSpeed, float frameTime, float friction, ref Vector2 velocity) @@ -349,16 +379,7 @@ namespace Content.Shared.Movement.Systems protected abstract bool CanSound(); - private bool TryGetSound( - bool weightless, - InputMoverComponent mover, - MobMoverComponent mobMover, - TransformComponent xform, - EntityQuery inventoryQuery, - EntityQuery containerQuery, - EntityQuery footQuery, - - [NotNullWhen(true)] out SoundSpecifier? sound) + private bool TryGetSound(bool weightless, InputMoverComponent mover, MobMoverComponent mobMover, TransformComponent xform, [NotNullWhen(true)] out SoundSpecifier? sound) { sound = null; @@ -392,21 +413,18 @@ namespace Content.Shared.Movement.Systems if (mobMover.StepSoundDistance < distanceNeeded) return false; mobMover.StepSoundDistance -= distanceNeeded; - EntityUid? shoes = null; - if (inventoryQuery.TryGetComponent(mover.Owner, out var inventory) && - containerQuery.TryGetComponent(mover.Owner, out var containerManager) && - _inventory.TryGetSlotEntity(mover.Owner, "shoes", out shoes, inventory, containerManager) && - footQuery.TryGetComponent(shoes, out var modifier)) + if (_inventory.TryGetSlotEntity(mover.Owner, "shoes", out var shoes) && + EntityManager.TryGetComponent(shoes, out var modifier)) { sound = modifier.Sound; return true; } - return TryGetFootstepSound(coordinates, shoes != null, footQuery, out sound); + return TryGetFootstepSound(coordinates, shoes != null, out sound); } - private bool TryGetFootstepSound(EntityCoordinates coordinates, bool haveShoes, EntityQuery footQuery, [NotNullWhen(true)] out SoundSpecifier? sound) + private bool TryGetFootstepSound(EntityCoordinates coordinates, bool haveShoes, [NotNullWhen(true)] out SoundSpecifier? sound) { sound = null; var gridUid = coordinates.GetGridUid(EntityManager); @@ -414,7 +432,7 @@ namespace Content.Shared.Movement.Systems // Fallback to the map if (gridUid == null) { - if (footQuery.TryGetComponent(coordinates.GetMapUid(EntityManager), out var modifier)) + if (TryComp(coordinates.GetMapUid(EntityManager), out var modifier)) { sound = modifier.Sound; return true; @@ -426,20 +444,17 @@ namespace Content.Shared.Movement.Systems var grid = _mapManager.GetGrid(gridUid.Value); var tile = grid.GetTileRef(coordinates); - if (tile.IsSpace(_tileDefinitionManager)) - return false; + if (tile.IsSpace(_tileDefinitionManager)) return false; // If the coordinates have a FootstepModifier component // i.e. component that emit sound on footsteps emit that sound - var anchoredEnumerator = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices); - - while (anchoredEnumerator.MoveNext(out var maybeFootstep)) + foreach (var maybeFootstep in grid.GetAnchoredEntities(tile.GridIndices)) { - if (!footQuery.TryGetComponent(maybeFootstep, out var footstep)) - continue; - - sound = footstep.Sound; - return true; + if (EntityManager.TryGetComponent(maybeFootstep, out FootstepModifierComponent? footstep)) + { + sound = footstep.Sound; + return true; + } } // Walking on a tile.