diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 2d589da0dd..32c6247f05 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -68,8 +68,12 @@ namespace Content.Client.Physics.Controllers var xformQuery = GetEntityQuery(); var moverQuery = GetEntityQuery(); var relayTargetQuery = GetEntityQuery(); + var mobMoverQuery = GetEntityQuery(); + var pullableQuery = GetEntityQuery(); + var physicsQuery = GetEntityQuery(); + var modifierQuery = GetEntityQuery(); - if (!TryComp(player, out InputMoverComponent? mover) || + if (!moverQuery.TryGetComponent(player, out var mover) || !xformQuery.TryGetComponent(player, out var xform)) { return; @@ -106,24 +110,24 @@ namespace Content.Client.Physics.Controllers { foreach (var joint in jointComponent.GetJoints.Values) { - if (TryComp(joint.BodyAUid, out PhysicsComponent? physics)) + if (physicsQuery.TryGetComponent(joint.BodyAUid, out var physics)) physics.Predict = true; - if (TryComp(joint.BodyBUid, out physics)) + if (physicsQuery.TryGetComponent(joint.BodyBUid, out physics)) physics.Predict = true; } } // If we're being pulled then we won't predict anything and will receive server lerps so it looks way smoother. - if (TryComp(player, out SharedPullableComponent? pullableComp)) + if (pullableQuery.TryGetComponent(player, out var pullableComp)) { - if (pullableComp.Puller is {Valid: true} puller && TryComp(puller, out var pullerBody)) + if (pullableComp.Puller is {Valid: true} puller && TryComp(puller, out var pullerBody)) { pullerBody.Predict = false; body.Predict = false; if (TryComp(player, out var playerPuller) && playerPuller.Pulling != null && - TryComp(playerPuller.Pulling, out var pulledBody)) + physicsQuery.TryGetComponent(playerPuller.Pulling, out var pulledBody)) { pulledBody.Predict = false; } @@ -131,7 +135,19 @@ namespace Content.Client.Physics.Controllers } // Server-side should just be handled on its own so we'll just do this shizznit - HandleMobMovement(player, mover, physicsUid, body, xformMover, frameTime, xformQuery, moverQuery, relayTargetQuery); + HandleMobMovement( + player, + mover, + physicsUid, + body, + xformMover, + frameTime, + xformQuery, + moverQuery, + mobMoverQuery, + relayTargetQuery, + pullableQuery, + modifierQuery); } protected override bool CanSound() diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index bf56b32989..a4c308e9bc 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -3,6 +3,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; +using Content.Shared.Pulling.Components; using Content.Shared.Shuttles.Components; using Content.Shared.Shuttles.Systems; using Robust.Server.GameObjects; @@ -65,11 +66,14 @@ namespace Content.Server.Physics.Controllers var relayTargetQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); var moverQuery = GetEntityQuery(); + var mobMoverQuery = GetEntityQuery(); + var pullableQuery = GetEntityQuery(); + var inputQueryEnumerator = AllEntityQuery(); + var modifierQuery = GetEntityQuery(); - foreach (var mover in EntityQuery(true)) + while (inputQueryEnumerator.MoveNext(out var uid, out var mover)) { - var uid = mover.Owner; - EntityUid physicsUid = uid; + var physicsUid = uid; if (relayQuery.HasComponent(uid)) continue; @@ -97,7 +101,18 @@ namespace Content.Server.Physics.Controllers continue; } - HandleMobMovement(uid, mover, physicsUid, body, xformMover, frameTime, xformQuery, moverQuery, relayTargetQuery); + HandleMobMovement(uid, + mover, + physicsUid, + body, + xformMover, + frameTime, + xformQuery, + moverQuery, + mobMoverQuery, + relayTargetQuery, + pullableQuery, + modifierQuery); } HandleShuttleMovement(frameTime); diff --git a/Content.Shared/Movement/Components/InputMoverComponent.cs b/Content.Shared/Movement/Components/InputMoverComponent.cs index 49a82df1d3..0b87e4311d 100644 --- a/Content.Shared/Movement/Components/InputMoverComponent.cs +++ b/Content.Shared/Movement/Components/InputMoverComponent.cs @@ -1,5 +1,6 @@ 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 @@ -61,8 +62,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)] - public float LerpAccumulator; + [ViewVariables(VVAccess.ReadWrite), DataField("lerpTarget", customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan LerpTarget; 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 186c4f8756..fbf0346d7b 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -83,7 +83,7 @@ namespace Content.Shared.Movement.Systems component.RelativeRotation = state.RelativeRotation; component.TargetRelativeRotation = state.TargetRelativeRotation; component.RelativeEntity = state.RelativeEntity; - component.LerpAccumulator = state.LerpAccumulator; + component.LerpTarget = state.LerpAccumulator; } private void OnInputGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args) @@ -94,7 +94,7 @@ namespace Content.Shared.Movement.Systems component.RelativeRotation, component.TargetRelativeRotation, component.RelativeEntity, - component.LerpAccumulator); + component.LerpTarget); } private void ShutdownInput() @@ -121,13 +121,73 @@ namespace Content.Shared.Movement.Systems public void ResetCamera(EntityUid uid) { - if (CameraRotationLocked || !TryComp(uid, out var mover) || mover.TargetRelativeRotation.Equals(Angle.Zero)) + 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)) 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; @@ -140,12 +200,7 @@ namespace Content.Shared.Movement.Systems public Angle GetParentGridAngle(InputMoverComponent mover) { - var rotation = mover.RelativeRotation; - - if (TryComp(mover.RelativeEntity, out var relativeXform)) - return (relativeXform.WorldRotation + rotation); - - return rotation; + return GetParentGridAngle(mover, GetEntityQuery()); } private void OnFollowedParentChange(EntityUid uid, FollowedComponent component, ref EntParentChangedMessage args) @@ -185,7 +240,7 @@ namespace Content.Shared.Movement.Systems component.RelativeEntity = relative; component.TargetRelativeRotation = Angle.Zero; component.RelativeRotation = Angle.Zero; - component.LerpAccumulator = 0f; + component.LerpTarget = TimeSpan.Zero; Dirty(component); return; } @@ -193,16 +248,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.LerpAccumulator != 0f) + if (component.LerpTarget >= Timing.CurTime) { - component.LerpAccumulator = 0f; + component.LerpTarget = TimeSpan.Zero; Dirty(component); } return; } - component.LerpAccumulator = InputMoverComponent.LerpTime; + component.LerpTarget = TimeSpan.FromSeconds(InputMoverComponent.LerpTime) + Timing.CurTime; Dirty(component); } @@ -233,18 +288,17 @@ namespace Content.Shared.Movement.Systems // Relay the fact we had any movement event. // TODO: Ideally we'd do these in a tick instead of out of sim. - var owner = moverComp.Owner; var moveEvent = new MoveInputEvent(entity); - RaiseLocalEvent(owner, ref moveEvent); + RaiseLocalEvent(entity, ref moveEvent); // For stuff like "Moving out of locker" or the likes // We'll relay a movement input to the parent. - if (_container.IsEntityInContainer(owner) && - TryComp(owner, out var xform) && + if (_container.IsEntityInContainer(entity) && + TryComp(entity, out var xform) && xform.ParentUid.IsValid() && - _mobState.IsAlive(owner)) + _mobState.IsAlive(entity)) { - var relayMoveEvent = new ContainerRelayMovementEntityEvent(owner); + var relayMoveEvent = new ContainerRelayMovementEntityEvent(entity); RaiseLocalEvent(xform.ParentUid, ref relayMoveEvent); } @@ -531,16 +585,16 @@ namespace Content.Shared.Movement.Systems /// public Angle TargetRelativeRotation; public EntityUid? RelativeEntity; - public float LerpAccumulator = 0f; + public TimeSpan LerpAccumulator; - public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, float lerpAccumulator) + public InputMoverComponentState(MoveButtons buttons, bool canMove, Angle relativeRotation, Angle targetRelativeRotation, EntityUid? relativeEntity, TimeSpan lerpTarget) { Buttons = buttons; CanMove = canMove; RelativeRotation = relativeRotation; TargetRelativeRotation = targetRelativeRotation; RelativeEntity = relativeEntity; - LerpAccumulator = lerpAccumulator; + LerpAccumulator = lerpTarget; } } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 9aec5e07f6..42e9b6f49a 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -108,10 +108,11 @@ namespace Content.Shared.Movement.Systems float frameTime, EntityQuery xformQuery, EntityQuery moverQuery, - EntityQuery relayTargetQuery) + EntityQuery mobMoverQuery, + EntityQuery relayTargetQuery, + EntityQuery pullableQuery, + EntityQuery modifierQuery) { - DebugTools.Assert(!UsedMobMovement.ContainsKey(uid)); - bool canMove = mover.CanMove; if (relayTargetQuery.TryGetComponent(uid, out var relayTarget) && relayTarget.Entities.Count > 0) { @@ -135,59 +136,11 @@ namespace Content.Shared.Movement.Systems } // Update relative movement - if (mover.LerpAccumulator > 0f) + if (mover.LerpTarget < Timing.CurTime) { - Dirty(mover); - mover.LerpAccumulator -= frameTime; - - if (mover.LerpAccumulator <= 0f) + if (TryUpdateRelative(mover, xform, xformQuery)) { - 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; - } + Dirty(mover); } } @@ -223,7 +176,7 @@ namespace Content.Shared.Movement.Systems if (!canMove || physicsComponent.BodyStatus != BodyStatus.OnGround - || TryComp(uid, out SharedPullableComponent? pullable) && pullable.BeingPulled) + || pullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled) { UsedMobMovement[uid] = false; return; @@ -249,14 +202,14 @@ namespace Content.Shared.Movement.Systems touching = ev.CanMove; if (!touching && TryComp(uid, out var mobMover)) - touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsComponent); + touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent); } } // Regular movement. // Target velocity. // This is relative to the map / grid we're on. - var moveSpeedComponent = CompOrNull(uid); + var moveSpeedComponent = modifierQuery.CompOrNull(uid); var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; @@ -308,8 +261,8 @@ namespace Content.Shared.Movement.Systems // 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? - if (!weightless && TryComp(mover.Owner, out var mobMover) && - TryGetSound(weightless, mover, mobMover, xform, out var sound)) + if (!weightless && mobMoverQuery.TryGetComponent(uid, out var mobMover) && + TryGetSound(weightless, uid, mover, mobMover, xform, out var sound)) { var soundModifier = mover.Sprinting ? 1.0f : FootstepWalkingAddedVolumeMultiplier; @@ -347,7 +300,8 @@ namespace Content.Shared.Movement.Systems { var speed = velocity.Length; - if (speed < minimumFrictionSpeed) return; + if (speed < minimumFrictionSpeed) + return; var drop = 0f; @@ -356,7 +310,8 @@ namespace Content.Shared.Movement.Systems var newSpeed = MathF.Max(0f, speed - drop); - if (newSpeed.Equals(speed)) return; + if (newSpeed.Equals(speed)) + return; newSpeed /= speed; velocity *= newSpeed; @@ -370,7 +325,8 @@ namespace Content.Shared.Movement.Systems var currentSpeed = Vector2.Dot(currentVelocity, wishDir); var addSpeed = wishSpeed - currentSpeed; - if (addSpeed <= 0f) return; + if (addSpeed <= 0f) + return; var accelSpeed = accel * frameTime * wishSpeed; accelSpeed = MathF.Min(accelSpeed, addSpeed); @@ -386,9 +342,9 @@ namespace Content.Shared.Movement.Systems /// /// Used for weightlessness to determine if we are near a wall. /// - private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, PhysicsComponent collider) + private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformComponent transform, MobMoverComponent mover, EntityUid physicsUid, PhysicsComponent collider) { - var enlargedAABB = _lookup.GetWorldAABB(collider.Owner, transform).Enlarged(mover.GrabRangeVV); + var enlargedAABB = _lookup.GetWorldAABB(physicsUid, transform).Enlarged(mover.GrabRangeVV); foreach (var otherCollider in broadPhaseSystem.GetCollidingEntities(transform.MapID, enlargedAABB)) { @@ -412,11 +368,12 @@ namespace Content.Shared.Movement.Systems protected abstract bool CanSound(); - private bool TryGetSound(bool weightless, InputMoverComponent mover, MobMoverComponent mobMover, TransformComponent xform, [NotNullWhen(true)] out SoundSpecifier? sound) + private bool TryGetSound(bool weightless, EntityUid uid, InputMoverComponent mover, MobMoverComponent mobMover, TransformComponent xform, [NotNullWhen(true)] out SoundSpecifier? sound) { sound = null; - if (!CanSound() || !_tags.HasTag(mover.Owner, "FootstepSound")) return false; + if (!CanSound() || !_tags.HasTag(uid, "FootstepSound")) + return false; var coordinates = xform.Coordinates; var distanceNeeded = mover.Sprinting ? StepSoundMoveDistanceRunning : StepSoundMoveDistanceWalking; @@ -443,27 +400,28 @@ namespace Content.Shared.Movement.Systems mobMover.LastPosition = coordinates; - if (mobMover.StepSoundDistance < distanceNeeded) return false; + if (mobMover.StepSoundDistance < distanceNeeded) + return false; mobMover.StepSoundDistance -= distanceNeeded; - if (TryComp(mover.Owner, out var moverModifier)) + if (TryComp(uid, out var moverModifier)) { sound = moverModifier.Sound; return true; } - if (_inventory.TryGetSlotEntity(mover.Owner, "shoes", out var shoes) && + if (_inventory.TryGetSlotEntity(uid, "shoes", out var shoes) && TryComp(shoes, out var modifier)) { sound = modifier.Sound; return true; } - return TryGetFootstepSound(xform, shoes != null, out sound); + return TryGetFootstepSound(uid, xform, shoes != null, out sound); } - private bool TryGetFootstepSound(TransformComponent xform, bool haveShoes, [NotNullWhen(true)] out SoundSpecifier? sound) + private bool TryGetFootstepSound(EntityUid uid, TransformComponent xform, bool haveShoes, [NotNullWhen(true)] out SoundSpecifier? sound) { sound = null; @@ -480,7 +438,7 @@ namespace Content.Shared.Movement.Systems } var position = grid.LocalToTile(xform.Coordinates); - var soundEv = new GetFootstepSoundEvent(xform.Owner); + var soundEv = new GetFootstepSoundEvent(uid); // If the coordinates have a FootstepModifier component // i.e. component that emit sound on footsteps emit that sound