using Content.Shared.Alert; using Content.Shared.CCVar; using Content.Shared.Friction; using Content.Shared.Movement.Components; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; using Robust.Client.Physics; using Robust.Client.Player; using Robust.Shared.Configuration; using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Client.PhysicsSystem.Controllers; public sealed class MoverController : SharedMoverController { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; 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 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. 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? } private void OnRelayPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) { PhysicsSystem.UpdateIsPredicted(entity.Owner); PhysicsSystem.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) { PhysicsSystem.UpdateIsPredicted(entity.Owner); PhysicsSystem.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 OnPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) { SetMoveInput(entity, MoveButtons.None); } public override void UpdateBeforeSolve(bool prediction, float frameTime) { base.UpdateBeforeSolve(prediction, frameTime); if (_playerManager.LocalEntity is not {Valid: true} player) return; if (RelayQuery.TryGetComponent(player, out var relayMover)) HandleClientsideMovement(relayMover.RelayEntity, frameTime); HandleClientsideMovement(player, frameTime); } private void HandleClientsideMovement(EntityUid player, float frameTime) { if (!MoverQuery.TryGetComponent(player, out var mover)) { return; } // Server-side should just be handled on its own so we'll just do this shizznit HandleMobMovement((player, mover), frameTime); } protected override bool CanSound() { return _timing is { IsFirstTimePredicted: true, InSimulation: true }; } public override void SetSprinting(Entity entity, ushort subTick, bool walking) { // Logger.Info($"[{_gameTiming.CurTick}/{subTick}] Sprint: {enabled}"); base.SetSprinting(entity, subTick, walking); if (walking && _cfg.GetCVar(CCVars.ToggleWalk)) _alerts.ShowAlert(entity.Owner, WalkingAlert, showCooldown: false, autoRemove: false); else _alerts.ClearAlert(entity.Owner, WalkingAlert); } }