using Sandbox.Utility; namespace Sandbox; public static partial class Input { /// /// What's our current player index (for input scoping)? /// -1 is the default behavior, where it'll accept keyboard AND gamepad inputs. /// Anything above that, is targeting a specific controller. /// internal static int CurrentPlayerScope { get; private set; } = -1; /// /// How many controllers are active right now? /// public static int ControllerCount => Controller.All.Count(); /// /// Whether or not the Virtual Cursor should show when using a controller. Disable this to control the cursor manually. /// public static bool EnableVirtualCursor { get; set; } = true; /// /// Tries to find the current controller to use. /// internal static Controller CurrentController { get { // Fallback if we're not using any input scoping if ( CurrentPlayerScope == -1 ) return Controller.First; // Out of range? if ( CurrentPlayerScope >= Controller.All.Count() ) { return null; } if ( Controller.All.ElementAt( CurrentPlayerScope ) is { } controller ) { return controller; } return null; } } /// /// An analog input, when fetched, is between -1 and 1 (0 being default) /// public static float GetAnalog( InputAnalog analog ) { if ( Suppressed ) return default; if ( Input.CurrentController is { } controller && UsingController ) { return controller.GetAxis( analog.ToAxis() ); } return 0f; } /// /// Processes controller inputs based on a player index (for input scoping). This can be called many times a frame. /// private static void ProcessControllerInput( int playerIndex = -1 ) { CurrentPlayerScope = playerIndex; // Default input accepts gamepad and keyboard and mouse, so we don't want to reset analogs if ( CurrentPlayerScope != -1 ) { // Reset analogs AnalogLook = default; AnalogMove = 0; } if ( Input.CurrentController is { } controller && UsingController ) { // Use controller's input context // Doesn't need to be flipped, we do this once a frame for each controller. controller.InputContext?.Push(); var lookX = controller.GetAxis( NativeEngine.GameControllerAxis.RightX ) * Time.Delta * Preferences.ControllerLookYawSpeed; var lookY = controller.GetAxis( NativeEngine.GameControllerAxis.RightY ) * Time.Delta * Preferences.ControllerLookPitchSpeed; AnalogLook = new Angles( lookY, -lookX, 0 ); var moveX = controller.GetAxis( NativeEngine.GameControllerAxis.LeftX ); var moveY = controller.GetAxis( NativeEngine.GameControllerAxis.LeftY ); AnalogMove = new Vector3( -moveY, -moveX, 0 ); MotionData = new() { Gyroscope = controller.Gyroscope, Accelerometer = controller.Accelerometer }; controller.UpdateHaptics(); } } /// /// Push a specific player scope to be active /// public static IDisposable PlayerScope( int index ) { index = index.Clamp( 0, int.MaxValue ); var old = CurrentPlayerScope; // Process input for our new scope ProcessControllerInput( index ); return DisposeAction.Create( () => { if ( CurrentPlayerScope == index ) { ProcessControllerInput( old ); } } ); } }