using SDL2; namespace Nerfed.Runtime; public class GamePad { private const int maxGamePads = 4; private static readonly GamePad[] gamePads = new GamePad[maxGamePads]; private static readonly GamePad dummy = new GamePad(); public IntPtr Handle { get; private set; } public bool IsConnected => Handle != IntPtr.Zero; public int JoystickInstanceId { get; private set; } = -1; private readonly ButtonState[] buttonStates; private readonly ButtonState[] lastButtonStates; static GamePad() { for (int i = 0; i < maxGamePads; i++) { gamePads[i] = new GamePad(); } } private GamePad() { int gamePadButtonCount = Enum.GetValues().Length; buttonStates = new ButtonState[gamePadButtonCount]; lastButtonStates = new ButtonState[gamePadButtonCount]; } public bool IsButtonDown(GamePadButton button) { return buttonStates[(int)button] == ButtonState.Pressed; } private void Register(IntPtr handle) { Handle = handle; IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(handle); JoystickInstanceId = SDL.SDL_JoystickInstanceID(joystickHandle); Log.Info("Controller connected"); } private void UnRegister() { Handle = IntPtr.Zero; JoystickInstanceId = -1; Log.Info("Controller disconnected"); } public static GamePad Get(int slot) { return slot >= 0 && slot < maxGamePads ? gamePads[slot] : dummy; } internal static void Update() { for (int i = 0; i < maxGamePads; i++) { GamePad gamePad = gamePads[i]; if (gamePad.IsConnected) { Array.Copy(gamePad.buttonStates, gamePad.lastButtonStates, gamePad.buttonStates.Length); } } } internal static void ProcessEvent(ref SDL.SDL_Event ev) { switch (ev.type) { case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED: ProcessDeviceAdded(ref ev.cdevice); break; case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED: ProcessDeviceRemoved(ref ev.cdevice); break; case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN: ProcessButtonDown(ref ev.cbutton); break; case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP: ProcessButtonUp(ref ev.cbutton); break; case SDL.SDL_EventType.SDL_CONTROLLERAXISMOTION: ProcessAxisMotion(ref ev.caxis); break; case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADDOWN: ProcessTouchPadDown(ref ev.ctouchpad); break; case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADUP: ProcessTouchPadUp(ref ev.ctouchpad); break; case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADMOTION: ProcessTouchPadMotion(ref ev.ctouchpad); break; case SDL.SDL_EventType.SDL_CONTROLLERSENSORUPDATE: ProcessSensorUpdate(ref ev.sensor); break; } } private static void ProcessDeviceAdded(ref SDL.SDL_ControllerDeviceEvent ev) { int index = ev.which; if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE) { int slot = -1; for (int i = 0; i < maxGamePads; i++) { if (!gamePads[i].IsConnected) { slot = i; break; } } if (slot == -1) { Log.Warning("Too many gamepads connected"); return; } IntPtr handle = SDL.SDL_GameControllerOpen(index); if (handle == IntPtr.Zero) { Log.Error($"Failed to open gamepad: {SDL.SDL_GetError()}"); return; } gamePads[slot].Register(handle); } } private static GamePad GetByJoystickId(int joystickId) { for (int slot = 0; slot < maxGamePads; slot++) { GamePad gamePad = gamePads[slot]; if (gamePad.IsConnected && gamePad.JoystickInstanceId == joystickId) { return gamePad; } } return null; } private static void ProcessDeviceRemoved(ref SDL.SDL_ControllerDeviceEvent ev) { GamePad gamePad = GetByJoystickId(ev.which); gamePad?.UnRegister(); } private static void ProcessButtonDown(ref SDL.SDL_ControllerButtonEvent ev) { if (TryConvertButton(ev.button, out GamePadButton button)) { GamePad gamePad = GetByJoystickId(ev.which); gamePad.buttonStates[(int)button] = ButtonState.Pressed; } } private static void ProcessButtonUp(ref SDL.SDL_ControllerButtonEvent ev) { if (TryConvertButton(ev.button, out GamePadButton button)) { GamePad gamePad = GetByJoystickId(ev.which); gamePad.buttonStates[(int)button] = ButtonState.Released; } } private static void ProcessAxisMotion(ref SDL.SDL_ControllerAxisEvent ev) { } private static void ProcessTouchPadDown(ref SDL.SDL_ControllerTouchpadEvent ev) { } private static void ProcessTouchPadUp(ref SDL.SDL_ControllerTouchpadEvent ev) { } private static void ProcessTouchPadMotion(ref SDL.SDL_ControllerTouchpadEvent ev) { } private static void ProcessSensorUpdate(ref SDL.SDL_SensorEvent ev) { } private static bool TryConvertButton(byte rawButton, out GamePadButton button) { GamePadButton? result = rawButton switch { (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A => GamePadButton.A, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B => GamePadButton.B, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X => GamePadButton.X, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y => GamePadButton.Y, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK => GamePadButton.Back, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START => GamePadButton.Start, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK => GamePadButton.LeftStick, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK => GamePadButton.RightStick, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER => GamePadButton.LeftShoulder, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER => GamePadButton.RightShoulder, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP => GamePadButton.DPadUp, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN => GamePadButton.DPadDown, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT => GamePadButton.DPadLeft, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT => GamePadButton.DPadRight, (byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD => GamePadButton.BigButton, _ => null }; button = result.GetValueOrDefault(); return result.HasValue; } }