diff --git a/android/app/src/main/assets/Config/Supermodel.ini b/android/app/src/main/assets/Config/Supermodel.ini index e200fcb..f9e8dbe 100644 --- a/android/app/src/main/assets/Config/Supermodel.ini +++ b/android/app/src/main/assets/Config/Supermodel.ini @@ -42,24 +42,55 @@ InputSystem = sdl ; - top-right: TestA ; - left-middle: D-pad / steer ; - right-middle: throttle (top) / brake (bottom) -InputCoin1 = KEY_5 -InputStart1 = KEY_1 -InputServiceA = KEY_F1 +InputCoin1 = KEY_5,JOY1_BUTTON7 +InputStart1 = KEY_1,JOY1_BUTTON8 +InputServiceA = KEY_F1,JOY1_BUTTON13 InputTestA = KEY_F2 -InputJoyUp = KEY_UP -InputJoyDown = KEY_DOWN -InputJoyLeft = KEY_LEFT -InputJoyRight = KEY_RIGHT +InputJoyUp = KEY_UP,JOY1_POV1_UP,JOY1_YAXIS_NEG +InputJoyDown = KEY_DOWN,JOY1_POV1_DOWN,JOY1_YAXIS_POS +InputJoyLeft = KEY_LEFT,JOY1_POV1_LEFT,JOY1_XAXIS_NEG +InputJoyRight = KEY_RIGHT,JOY1_POV1_RIGHT,JOY1_XAXIS_POS InputSteeringLeft = KEY_LEFT InputSteeringRight = KEY_RIGHT -InputAccelerator = KEY_W -InputBrake = KEY_S +InputSteering = JOY1_XAXIS +InputAccelerator = KEY_W,JOY1_RZAXIS_POS +InputBrake = KEY_S,JOY1_ZAXIS_POS + +; Manual transmission (LB/RB + 4-speed on face buttons) +InputGearShiftUp = KEY_Y,JOY1_BUTTON6 +InputGearShiftDown = KEY_H,JOY1_BUTTON5 +InputGearShift1 = KEY_Q,JOY1_BUTTON3 +InputGearShift2 = KEY_W,JOY1_BUTTON1 +InputGearShift3 = KEY_E,JOY1_BUTTON4 +InputGearShift4 = KEY_R,JOY1_BUTTON2 +InputGearShiftN = KEY_T + +; Fighting game buttons (X/A/B/Y) +InputPunch = KEY_A,JOY1_BUTTON3 +InputKick = KEY_S,JOY1_BUTTON1 +InputGuard = KEY_D,JOY1_BUTTON2 +InputEscape = KEY_F,JOY1_BUTTON4 + +; Spikeout buttons (A/X/Y + B/RB) +InputShift = KEY_A,JOY1_BUTTON2,JOY1_BUTTON6 +InputBeat = KEY_S,JOY1_BUTTON1 +InputCharge = KEY_D,JOY1_BUTTON3 +InputJump = KEY_F,JOY1_BUTTON4 + +; Virtua Striker buttons (A/B/X) +InputShortPass = KEY_A,JOY1_BUTTON1 +InputLongPass = KEY_S,JOY1_BUTTON2 +InputShoot = KEY_D,JOY1_BUTTON3 + +; View change (single-button games) +InputViewChange = KEY_A,JOY1_BUTTON4 + +; Handbrake (Sega Rally 2) +InputHandBrake = KEY_S,JOY1_BUTTON2 InputVR1 = KEY_A,JOY1_BUTTON1 InputVR2 = KEY_S,JOY1_BUTTON2 InputVR3 = KEY_D,JOY1_BUTTON3 InputVR4 = KEY_F,JOY1_BUTTON4 -InputViewChange = KEY_A,JOY1_BUTTON1 -InputHandBrake = KEY_S,JOY1_BUTTON2 InputRearBrake = KEY_S,JOY1_BUTTON2 InputMusicSelect = KEY_D,JOY1_BUTTON3 InputTwinJoyTurnLeft = KEY_Q,JOY1_RXAXIS_NEG diff --git a/android/app/src/main/cpp/android_input_system.cpp b/android/app/src/main/cpp/android_input_system.cpp index e1ad190..694977d 100644 --- a/android/app/src/main/cpp/android_input_system.cpp +++ b/android/app/src/main/cpp/android_input_system.cpp @@ -1,12 +1,36 @@ #include "android_input_system.h" #include +#include #include AndroidInputSystem::AndroidInputSystem() : CInputSystem("android-sdl"), m_keys(SDL_NUM_SCANCODES, 0) { + std::memset(&m_mouseDetails, 0, sizeof(m_mouseDetails)); + std::strncpy(m_mouseDetails.name, "Touchscreen", MAX_NAME_LENGTH); + m_mouseDetails.name[MAX_NAME_LENGTH] = '\0'; + m_mouseDetails.isAbsolute = true; +} + +AndroidInputSystem::~AndroidInputSystem() +{ + CloseControllers(); +} + +void AndroidInputSystem::SetGunTouchEnabled(bool enabled) +{ + if (m_gunTouchEnabled == enabled) + return; + m_gunTouchEnabled = enabled; + m_gunFingerActive = false; + m_gunFinger = 0; + m_mouseX = 0; + m_mouseY = 0; + m_mouseWheelDir = 0; + std::memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); + std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs)); } void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config) @@ -50,6 +74,16 @@ bool AndroidInputSystem::InitializeSystem() m_fingerHeldDir.clear(); m_fingerHeldKey.clear(); m_pulseUntilMs.clear(); + m_mouseX = 0; + m_mouseY = 0; + m_mouseWheelDir = 0; + std::memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); + std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs)); + m_gunFingerActive = false; + m_gunFinger = 0; + + SDL_GameControllerEventState(SDL_ENABLE); + RefreshControllers(); return true; } @@ -82,6 +116,8 @@ void AndroidInputSystem::PulseKeys(const DualScancode& sc, uint32_t durationMs) bool AndroidInputSystem::Poll() { + SDL_GameControllerUpdate(); + const uint32_t now = SDL_GetTicks(); for (auto it = m_pulseUntilMs.begin(); it != m_pulseUntilMs.end();) { @@ -95,6 +131,15 @@ bool AndroidInputSystem::Poll() ++it; } } + + for (int i = 0; i < kMouseButtons; i++) + { + if (m_mouseButtonPulseUntilMs[i] != 0 && now >= m_mouseButtonPulseUntilMs[i]) + { + m_mouseButtonPulseUntilMs[i] = 0; + m_mouseButtons[i] = 0; + } + } return true; } @@ -102,6 +147,11 @@ void AndroidInputSystem::HandleEvent(const SDL_Event& ev) { switch (ev.type) { + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + case SDL_CONTROLLERDEVICEREMAPPED: + RefreshControllers(); + break; case SDL_KEYDOWN: HandleKeyEvent(ev.key, true); break; @@ -135,26 +185,18 @@ void AndroidInputSystem::HandleKeyEvent(const SDL_KeyboardEvent& key, bool down) void AndroidInputSystem::HandleControllerButtonEvent(const SDL_ControllerButtonEvent& btn, bool down) { - // Basic defaults: - // - Start: Start1 - // - Right shoulder: Coin1 - // - Back: TestA - // - D-pad: menu navigation - // - A/B/X/Y: map to common keys for future bindings + // Controller buttons can be mapped in Supermodel.ini via JOY mappings, but we also + // synthesize a few "touch-style" keys for convenience in the UI / test menu. DualScancode sc; switch (btn.button) { case SDL_CONTROLLER_BUTTON_START: sc = m_touchStart; break; - case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: sc = m_touchCoin; break; - case SDL_CONTROLLER_BUTTON_BACK: sc = m_touchTest; break; + case SDL_CONTROLLER_BUTTON_BACK: sc = m_touchCoin; break; + case SDL_CONTROLLER_BUTTON_GUIDE: sc = m_touchService; break; case SDL_CONTROLLER_BUTTON_DPAD_UP: sc = m_touchJoyUp; break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: sc = m_touchJoyDown; break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: sc = m_touchJoyLeft; break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: sc = m_touchJoyRight; break; - case SDL_CONTROLLER_BUTTON_A: sc = {SDL_SCANCODE_Z, SDL_SCANCODE_UNKNOWN}; break; - case SDL_CONTROLLER_BUTTON_B: sc = {SDL_SCANCODE_X, SDL_SCANCODE_UNKNOWN}; break; - case SDL_CONTROLLER_BUTTON_X: sc = {SDL_SCANCODE_C, SDL_SCANCODE_UNKNOWN}; break; - case SDL_CONTROLLER_BUTTON_Y: sc = {SDL_SCANCODE_V, SDL_SCANCODE_UNKNOWN}; break; default: break; } @@ -186,6 +228,38 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) if (x > 0.75f && y < 0.25f) { PulseKeys(m_touchTest, 120); return; } } + // Lightgun/analog-gun games: use the touchscreen as an absolute "mouse" so + // existing MOUSE_XAXIS/MOUSE_YAXIS + MOUSE_LEFT_BUTTON mappings work without + // a physical mouse. + if (m_gunTouchEnabled) + { + if (down) + { + if (!m_gunFingerActive) + { + m_gunFingerActive = true; + m_gunFinger = tf.fingerId; + SetMousePosFromNormalized(x, y); + SetMouseButton(0, true); // left = trigger (held) + } + else + { + // Second touch while aiming: treat as offscreen/reload (right button pulse). + PulseMouseButton(2, 120); + } + } + else + { + if (m_gunFingerActive && tf.fingerId == m_gunFinger) + { + SetMouseButton(0, false); + m_gunFingerActive = false; + m_gunFinger = 0; + } + } + return; + } + // Held throttle/brake zone (right-middle): hold to accelerate/brake. // - Upper half: throttle (KEY_W) // - Lower half: brake (KEY_S) @@ -246,6 +320,15 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf) { + if (m_gunTouchEnabled) + { + if (m_gunFingerActive && tf.fingerId == m_gunFinger) + { + SetMousePosFromNormalized(tf.x, tf.y); + } + return; + } + // Update held d-pad direction. auto it = m_fingerHeldDir.find(tf.fingerId); if (it == m_fingerHeldDir.end()) @@ -268,6 +351,291 @@ void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf) HandleTouch(tf, true); } +int AndroidInputSystem::GetNumMice() +{ + return m_gunTouchEnabled ? 1 : 0; +} + +const MouseDetails* AndroidInputSystem::GetMouseDetails(int mseNum) +{ + if (!m_gunTouchEnabled) + return nullptr; + if (mseNum == ANY_MOUSE || mseNum == 0) + return &m_mouseDetails; + return nullptr; +} + +int AndroidInputSystem::GetMouseAxisValue(int mseNum, int axisNum) +{ + if (!m_gunTouchEnabled) + return 0; + if (mseNum != ANY_MOUSE && mseNum != 0) + return 0; + + switch (axisNum) + { + case AXIS_X: return m_mouseX; + case AXIS_Y: return m_mouseY; + default: return 0; + } +} + +int AndroidInputSystem::GetMouseWheelDir(int mseNum) +{ + if (!m_gunTouchEnabled) + return 0; + if (mseNum != ANY_MOUSE && mseNum != 0) + return 0; + return m_mouseWheelDir; +} + +bool AndroidInputSystem::IsMouseButPressed(int mseNum, int butNum) +{ + if (!m_gunTouchEnabled) + return false; + if (mseNum != ANY_MOUSE && mseNum != 0) + return false; + if (butNum < 0 || butNum >= kMouseButtons) + return false; + return m_mouseButtons[butNum] != 0; +} + +void AndroidInputSystem::SetMouseButton(int butNum, bool down) +{ + if (butNum < 0 || butNum >= kMouseButtons) + return; + m_mouseButtons[butNum] = down ? 1 : 0; + if (!down) + m_mouseButtonPulseUntilMs[butNum] = 0; +} + +void AndroidInputSystem::PulseMouseButton(int butNum, uint32_t durationMs) +{ + if (butNum < 0 || butNum >= kMouseButtons) + return; + m_mouseButtons[butNum] = 1; + m_mouseButtonPulseUntilMs[butNum] = SDL_GetTicks() + durationMs; +} + +void AndroidInputSystem::SetMousePosFromNormalized(float x, float y) +{ + // The emulator polls inputs with a fixed display geometry (currently 496x384). + // Use the same coordinate system so the core's mouse/lightgun normalization works. + constexpr int w = 496; + constexpr int h = 384; + const int px = (int)std::lround(std::clamp(x, 0.0f, 1.0f) * (float)(w - 1)); + const int py = (int)std::lround(std::clamp(y, 0.0f, 1.0f) * (float)(h - 1)); + m_mouseX = std::clamp(px, 0, w - 1); + m_mouseY = std::clamp(py, 0, h - 1); +} + +void AndroidInputSystem::CloseControllers() +{ + for (auto& c : m_controllers) + { + if (c.controller) + { + SDL_GameControllerClose(c.controller); + c.controller = nullptr; + } + } + m_controllers.clear(); +} + +void AndroidInputSystem::RefreshControllers() +{ + CloseControllers(); + + const int n = SDL_NumJoysticks(); + for (int i = 0; i < n; i++) + { + if (!SDL_IsGameController(i)) + continue; + + SDL_GameController* controller = SDL_GameControllerOpen(i); + if (!controller) + continue; + + SDL_Joystick* js = SDL_GameControllerGetJoystick(controller); + if (!js) + { + SDL_GameControllerClose(controller); + continue; + } + + ControllerState state; + state.controller = controller; + state.instanceId = SDL_JoystickInstanceID(js); + + std::memset(&state.details, 0, sizeof(state.details)); + const char* name = SDL_GameControllerName(controller); + if (!name) name = "GameController"; + std::strncpy(state.details.name, name, MAX_NAME_LENGTH); + state.details.name[MAX_NAME_LENGTH] = '\0'; + + // Align with the desktop SDL "gamecontroller" path. + state.details.numAxes = 6; + state.details.numPOVs = 4; + state.details.numButtons = 17; + state.details.hasFFeedback = false; + + for (int a = 0; a < NUM_JOY_AXES; a++) + { + state.details.hasAxis[a] = false; + state.details.axisHasFF[a] = false; + std::strncpy(state.details.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH); + state.details.axisName[a][MAX_NAME_LENGTH] = '\0'; + } + + state.details.hasAxis[AXIS_X] = true; + state.details.hasAxis[AXIS_Y] = true; + state.details.hasAxis[AXIS_Z] = true; + state.details.hasAxis[AXIS_RX] = true; + state.details.hasAxis[AXIS_RY] = true; + state.details.hasAxis[AXIS_RZ] = true; + + m_controllers.push_back(state); + } +} + +int AndroidInputSystem::GetNumJoysticks() +{ + return static_cast(m_controllers.size()); +} + +const JoyDetails* AndroidInputSystem::GetJoyDetails(int joyNum) +{ + if (joyNum < 0 || joyNum >= static_cast(m_controllers.size())) + return nullptr; + return &m_controllers[static_cast(joyNum)].details; +} + +int AndroidInputSystem::AxisValueFor(const ControllerState& c, int axisNum) const +{ + if (!c.controller) + return 0; + + switch (axisNum) + { + case AXIS_X: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTX); + case AXIS_Y: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTY); + case AXIS_Z: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + case AXIS_RX: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTX); + case AXIS_RY: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTY); + case AXIS_RZ: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + default: return 0; + } +} + +bool AndroidInputSystem::PovPressedFor(const ControllerState& c, int povDir) const +{ + if (!c.controller) + return false; + + switch (povDir) + { + case POV_UP: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_UP) != 0; + case POV_DOWN: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0; + case POV_LEFT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0; + case POV_RIGHT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0; + default: return false; + } +} + +bool AndroidInputSystem::ButtonPressedFor(const ControllerState& c, int butNum) const +{ + if (!c.controller) + return false; + + // Match the desktop SDLInputSystem "useGameController" mapping. + switch (butNum) + { + case 0: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_A) != 0; + case 1: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_B) != 0; + case 2: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_X) != 0; + case 3: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_Y) != 0; + case 4: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0; + case 5: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0; + case 6: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_BACK) != 0; + case 7: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_START) != 0; + case 8: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) != 0; + case 9: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) != 0; + case 10: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE1) != 0; + case 11: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE2) != 0; + case 12: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_GUIDE) != 0; + case 13: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE3) != 0; + case 14: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE4) != 0; + case 15: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_MISC1) != 0; + case 16: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_TOUCHPAD) != 0; + default: return false; + } +} + +int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum) +{ + if (m_controllers.empty()) + return 0; + + if (joyNum == ANY_JOYSTICK) + { + int best = 0; + for (const auto& c : m_controllers) + { + const int v = AxisValueFor(c, axisNum); + if (std::abs(v) > std::abs(best)) + best = v; + } + return best; + } + + if (joyNum < 0 || joyNum >= static_cast(m_controllers.size())) + return 0; + + return AxisValueFor(m_controllers[static_cast(joyNum)], axisNum); +} + +bool AndroidInputSystem::IsJoyPOVInDir(int joyNum, int /*povNum*/, int povDir) +{ + if (m_controllers.empty()) + return false; + + if (joyNum == ANY_JOYSTICK) + { + for (const auto& c : m_controllers) + { + if (PovPressedFor(c, povDir)) + return true; + } + return false; + } + + if (joyNum < 0 || joyNum >= static_cast(m_controllers.size())) + return false; + + return PovPressedFor(m_controllers[static_cast(joyNum)], povDir); +} + +bool AndroidInputSystem::IsJoyButPressed(int joyNum, int butNum) +{ + if (m_controllers.empty()) + return false; + + if (joyNum == ANY_JOYSTICK) + { + for (const auto& c : m_controllers) + { + if (ButtonPressedFor(c, butNum)) + return true; + } + return false; + } + + if (joyNum < 0 || joyNum >= static_cast(m_controllers.size())) + return false; + + return ButtonPressedFor(m_controllers[static_cast(joyNum)], butNum); +} + SDL_Scancode AndroidInputSystem::ScancodeFromSupermodelKeyName(const char* keyName) const { if (!keyName || !keyName[0]) diff --git a/android/app/src/main/cpp/android_input_system.h b/android/app/src/main/cpp/android_input_system.h index 1d4e6ce..d959455 100644 --- a/android/app/src/main/cpp/android_input_system.h +++ b/android/app/src/main/cpp/android_input_system.h @@ -4,6 +4,7 @@ #include #include +#include #include #include "Inputs/InputSystem.h" @@ -17,7 +18,9 @@ class AndroidInputSystem final : public CInputSystem { public: AndroidInputSystem(); - ~AndroidInputSystem() override = default; + ~AndroidInputSystem() override; + + void SetGunTouchEnabled(bool enabled); // Called from the SDL event loop (main thread). void HandleEvent(const SDL_Event& ev); @@ -30,22 +33,22 @@ public: const char* GetKeyName(int keyIndex) override; bool IsKeyPressed(int /*kbdNum*/, int keyIndex) override; - int GetMouseAxisValue(int /*mseNum*/, int /*axisNum*/) override { return 0; } - int GetMouseWheelDir(int /*mseNum*/) override { return 0; } - bool IsMouseButPressed(int /*mseNum*/, int /*butNum*/) override { return false; } + int GetMouseAxisValue(int mseNum, int axisNum) override; + int GetMouseWheelDir(int mseNum) override; + bool IsMouseButPressed(int mseNum, int butNum) override; - int GetJoyAxisValue(int /*joyNum*/, int /*axisNum*/) override { return 0; } - bool IsJoyPOVInDir(int /*joyNum*/, int /*povNum*/, int /*povDir*/) override { return false; } - bool IsJoyButPressed(int /*joyNum*/, int /*butNum*/) override { return false; } + int GetJoyAxisValue(int joyNum, int axisNum) override; + bool IsJoyPOVInDir(int joyNum, int povNum, int povDir) override; + bool IsJoyButPressed(int joyNum, int butNum) override; bool ProcessForceFeedbackCmd(int /*joyNum*/, int /*axisNum*/, ForceFeedbackCmd /*ffCmd*/) override { return false; } int GetNumKeyboards() override { return 1; } - int GetNumMice() override { return 0; } - int GetNumJoysticks() override { return 0; } + int GetNumMice() override; + int GetNumJoysticks() override; const KeyDetails* GetKeyDetails(int /*kbdNum*/) override { return nullptr; } - const MouseDetails* GetMouseDetails(int /*mseNum*/) override { return nullptr; } - const JoyDetails* GetJoyDetails(int /*joyNum*/) override { return nullptr; } + const MouseDetails* GetMouseDetails(int mseNum) override; + const JoyDetails* GetJoyDetails(int joyNum) override; bool Poll() override; @@ -54,11 +57,22 @@ public: void UngrabMouse() override {} private: + static constexpr int kMouseButtons = 5; + + struct ControllerState { + SDL_GameController* controller = nullptr; + SDL_JoystickID instanceId = 0; + JoyDetails details{}; + }; + struct DualScancode { SDL_Scancode a = SDL_SCANCODE_UNKNOWN; SDL_Scancode b = SDL_SCANCODE_UNKNOWN; }; + void RefreshControllers(); + void CloseControllers(); + void SetKey(SDL_Scancode sc, bool down); void PulseKey(SDL_Scancode sc, uint32_t durationMs); void SetKeys(const DualScancode& sc, bool down); @@ -69,6 +83,14 @@ private: void HandleTouch(const SDL_TouchFingerEvent& tf, bool down); void HandleTouchMotion(const SDL_TouchFingerEvent& tf); + int AxisValueFor(const ControllerState& c, int axisNum) const; + bool ButtonPressedFor(const ControllerState& c, int butNum) const; + bool PovPressedFor(const ControllerState& c, int povDir) const; + + void SetMouseButton(int butNum, bool down); + void PulseMouseButton(int butNum, uint32_t durationMs); + void SetMousePosFromNormalized(float x, float y); + SDL_Scancode ScancodeFromSupermodelKeyName(const char* keyName) const; static SDL_Scancode ParseFirstKeyboardScancode(const std::string& mapping, const AndroidInputSystem& self); @@ -88,6 +110,18 @@ private: DualScancode m_touchThrottle{SDL_SCANCODE_W, SDL_SCANCODE_UNKNOWN}; DualScancode m_touchBrake{SDL_SCANCODE_S, SDL_SCANCODE_UNKNOWN}; + bool m_gunTouchEnabled = false; + SDL_FingerID m_gunFinger = 0; + bool m_gunFingerActive = false; + + MouseDetails m_mouseDetails{}; + int m_mouseX = 0; + int m_mouseY = 0; + int m_mouseWheelDir = 0; + uint8_t m_mouseButtons[kMouseButtons]{}; + uint32_t m_mouseButtonPulseUntilMs[kMouseButtons]{}; + + std::vector m_controllers; std::vector m_keys; // indexed by SDL_Scancode std::unordered_map m_fingerHeldDir; std::unordered_map m_fingerHeldKey; diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index 0ecce5e..68f0051 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -312,6 +312,10 @@ struct Super3Host { return false; } + const bool gunGame = + (game.inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0; + inputSystem.SetGunTouchEnabled(gunGame); + // Apply Supermodel.ini overrides (Global + [ game ]) after the loader has determined game->name. ApplyIniOverrides(game.name); ApplyAndroidHardOverrides(); diff --git a/android/app/src/main/res/layout/item_game.xml b/android/app/src/main/res/layout/item_game.xml index d9220ba..7c2026a 100644 --- a/android/app/src/main/res/layout/item_game.xml +++ b/android/app/src/main/res/layout/item_game.xml @@ -37,4 +37,3 @@ -