From e048fa7c737614ca691f25460e53fa80899b0d4d Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Fri, 19 Dec 2025 02:58:16 -0500 Subject: [PATCH] Added manual shifter to osd --- .../app/src/main/assets/Config/Supermodel.ini | 14 +- .../app/src/main/cpp/android_input_system.cpp | 99 + .../app/src/main/cpp/android_input_system.h | 13 + android/app/src/main/cpp/native-lib.cpp | 4 + .../com/izzy2lost/super3/AssetInstaller.kt | 16 +- .../com/izzy2lost/super3/Super3Activity.kt | 61 + android/app/src/main/res/drawable/shifter.xml | 1826 +++++++++++++++++ .../src/main/res/layout/overlay_controls.xml | 15 + 8 files changed, 2039 insertions(+), 9 deletions(-) create mode 100644 android/app/src/main/res/drawable/shifter.xml diff --git a/android/app/src/main/assets/Config/Supermodel.ini b/android/app/src/main/assets/Config/Supermodel.ini index c3abc88..4042416 100644 --- a/android/app/src/main/assets/Config/Supermodel.ini +++ b/android/app/src/main/assets/Config/Supermodel.ini @@ -57,13 +57,13 @@ InputAccelerator = KEY_W,JOY1_RZAXIS_POS InputBrake = KEY_X,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 +InputGearShiftUp = KEY_I,JOY1_BUTTON6 +InputGearShiftDown = KEY_K,JOY1_BUTTON5 +InputGearShift1 = KEY_7,JOY1_BUTTON3 +InputGearShift2 = KEY_8,JOY1_BUTTON1 +InputGearShift3 = KEY_9,JOY1_BUTTON4 +InputGearShift4 = KEY_0,JOY1_BUTTON2 +InputGearShiftN = KEY_6 ; Fighting game buttons (X/A/B/Y) InputPunch = KEY_A,JOY1_BUTTON3 diff --git a/android/app/src/main/cpp/android_input_system.cpp b/android/app/src/main/cpp/android_input_system.cpp index d792d1d..8da02d0 100644 --- a/android/app/src/main/cpp/android_input_system.cpp +++ b/android/app/src/main/cpp/android_input_system.cpp @@ -59,6 +59,13 @@ void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled) m_virtualJoyX = 0; } +void AndroidInputSystem::SetVirtualShifterMode(bool shift4, bool shiftUpDown) +{ + m_virtualShifterShift4 = shift4; + m_virtualShifterUpDown = shiftUpDown; + m_lastVirtualGear = -1; +} + bool AndroidInputSystem::UseVirtualWheel() const { return m_virtualWheelEnabled && m_controllers.empty(); @@ -113,6 +120,14 @@ void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config) m_touchThrottle.a = keySc(get("InputAccelerator", "KEY_W"), SDL_SCANCODE_W); m_touchBrake.a = keySc(get("InputBrake", "KEY_X"), SDL_SCANCODE_X); + + m_touchShiftUp.a = keySc(get("InputGearShiftUp", "KEY_I"), SDL_SCANCODE_I); + m_touchShiftDown.a = keySc(get("InputGearShiftDown", "KEY_K"), SDL_SCANCODE_K); + m_touchShift1.a = keySc(get("InputGearShift1", "KEY_7"), SDL_SCANCODE_7); + m_touchShift2.a = keySc(get("InputGearShift2", "KEY_8"), SDL_SCANCODE_8); + m_touchShift3.a = keySc(get("InputGearShift3", "KEY_9"), SDL_SCANCODE_9); + m_touchShift4.a = keySc(get("InputGearShift4", "KEY_0"), SDL_SCANCODE_0); + m_touchShiftN.a = keySc(get("InputGearShiftN", "KEY_6"), SDL_SCANCODE_6); } bool AndroidInputSystem::InitializeSystem() @@ -131,6 +146,7 @@ bool AndroidInputSystem::InitializeSystem() m_wheelFingerActive = false; m_wheelFinger = 0; m_virtualJoyX = 0; + m_lastVirtualGear = -1; SDL_GameControllerEventState(SDL_ENABLE); RefreshControllers(); @@ -265,6 +281,58 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) const float x = tf.x; const float y = tf.y; + // Virtual manual shifter (racing games): encoded in tf.x/tf.y from Java and keyed by a fixed fingerId. + if ((m_virtualShifterShift4 || m_virtualShifterUpDown) && tf.fingerId == 1108) + { + if (!down) + return; + + if (m_virtualShifterShift4) + { + const float dx = x - 0.5f; + const float dy = y - 0.5f; + const bool inNeutral = (std::abs(dx) < 0.18f && std::abs(dy) < 0.18f); + + int gear = m_lastVirtualGear; + if (inNeutral) + { + gear = 0; + } + else + { + const bool left = dx < 0.0f; + const bool upper = dy < 0.0f; + if (left && upper) gear = 1; + else if (left && !upper) gear = 2; + else if (!left && upper) gear = 3; + else gear = 4; + } + + if (gear != m_lastVirtualGear) + { + switch (gear) + { + case 0: PulseKeys(m_touchShiftN, 120); break; + case 1: PulseKeys(m_touchShift1, 120); break; + case 2: PulseKeys(m_touchShift2, 120); break; + case 3: PulseKeys(m_touchShift3, 120); break; + case 4: PulseKeys(m_touchShift4, 120); break; + default: break; + } + m_lastVirtualGear = gear; + } + return; + } + + // Up/down shifter: tap upper/lower half. + if (m_virtualShifterUpDown) + { + if (y < 0.5f) PulseKeys(m_touchShiftUp, 120); + else PulseKeys(m_touchShiftDown, 120); + return; + } + } + // Virtual steering wheel (racing games): encoded in tf.x from Java and keyed by a fixed fingerId. if (UseVirtualWheel()) { @@ -396,6 +464,37 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf) { + if (m_virtualShifterShift4 && tf.fingerId == 1108) + { + // Avoid accidental neutral when passing through center during motion. + const float dx = tf.x - 0.5f; + const float dy = tf.y - 0.5f; + if (std::abs(dx) < 0.18f && std::abs(dy) < 0.18f) + return; + + const bool left = dx < 0.0f; + const bool upper = dy < 0.0f; + int gear; + if (left && upper) gear = 1; + else if (left && !upper) gear = 2; + else if (!left && upper) gear = 3; + else gear = 4; + + if (gear != m_lastVirtualGear) + { + switch (gear) + { + case 1: PulseKeys(m_touchShift1, 120); break; + case 2: PulseKeys(m_touchShift2, 120); break; + case 3: PulseKeys(m_touchShift3, 120); break; + case 4: PulseKeys(m_touchShift4, 120); break; + default: break; + } + m_lastVirtualGear = gear; + } + return; + } + if (UseVirtualWheel()) { if (m_wheelFingerActive && tf.fingerId == m_wheelFinger) diff --git a/android/app/src/main/cpp/android_input_system.h b/android/app/src/main/cpp/android_input_system.h index 1b3fc81..d57f23d 100644 --- a/android/app/src/main/cpp/android_input_system.h +++ b/android/app/src/main/cpp/android_input_system.h @@ -22,6 +22,7 @@ public: void SetGunTouchEnabled(bool enabled); void SetVirtualWheelEnabled(bool enabled); + void SetVirtualShifterMode(bool shift4, bool shiftUpDown); // Called from the SDL event loop (main thread). void HandleEvent(const SDL_Event& ev); @@ -114,6 +115,14 @@ private: DualScancode m_touchThrottle{SDL_SCANCODE_W, SDL_SCANCODE_UNKNOWN}; DualScancode m_touchBrake{SDL_SCANCODE_X, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShiftUp{SDL_SCANCODE_I, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShiftDown{SDL_SCANCODE_K, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShift1{SDL_SCANCODE_7, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShift2{SDL_SCANCODE_8, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShift3{SDL_SCANCODE_9, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShift4{SDL_SCANCODE_0, SDL_SCANCODE_UNKNOWN}; + DualScancode m_touchShiftN{SDL_SCANCODE_6, SDL_SCANCODE_UNKNOWN}; + bool m_gunTouchEnabled = false; SDL_FingerID m_gunFinger = 0; bool m_gunFingerActive = false; @@ -124,6 +133,10 @@ private: int m_virtualJoyX = 0; JoyDetails m_virtualJoyDetails{}; + bool m_virtualShifterShift4 = false; + bool m_virtualShifterUpDown = false; + int m_lastVirtualGear = -1; + MouseDetails m_mouseDetails{}; int m_mouseX = 0; int m_mouseY = 0; diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index b8184b2..9c773bd 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -319,6 +319,10 @@ struct Super3Host { const bool vehicleGame = (game.inputs & (Game::INPUT_VEHICLE | Game::INPUT_HARLEY)) != 0; inputSystem.SetVirtualWheelEnabled(vehicleGame); + const bool shift4 = (game.inputs & Game::INPUT_SHIFT4) != 0; + const bool shiftUpDown = (game.inputs & Game::INPUT_SHIFTUPDOWN) != 0; + inputSystem.SetVirtualShifterMode(shift4, shiftUpDown); + // Apply Supermodel.ini overrides (Global + [ game ]) after the loader has determined game->name. ApplyIniOverrides(game.name); ApplyAndroidHardOverrides(); diff --git a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt index b9f1a04..512c7a0 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt @@ -45,12 +45,24 @@ object AssetInstaller { val lines = runCatching { ini.readLines() }.getOrNull() ?: return var changed = false + val replacements = + mapOf( + "InputBrake = KEY_S,JOY1_ZAXIS_POS" to "InputBrake = KEY_X,JOY1_ZAXIS_POS", + "InputGearShiftUp = KEY_Y,JOY1_BUTTON6" to "InputGearShiftUp = KEY_I,JOY1_BUTTON6", + "InputGearShiftDown = KEY_H,JOY1_BUTTON5" to "InputGearShiftDown = KEY_K,JOY1_BUTTON5", + "InputGearShift1 = KEY_Q,JOY1_BUTTON3" to "InputGearShift1 = KEY_7,JOY1_BUTTON3", + "InputGearShift2 = KEY_W,JOY1_BUTTON1" to "InputGearShift2 = KEY_8,JOY1_BUTTON1", + "InputGearShift3 = KEY_E,JOY1_BUTTON4" to "InputGearShift3 = KEY_9,JOY1_BUTTON4", + "InputGearShift4 = KEY_R,JOY1_BUTTON2" to "InputGearShift4 = KEY_0,JOY1_BUTTON2", + "InputGearShiftN = KEY_T" to "InputGearShiftN = KEY_6", + ) val updated = lines.map { line -> val trimmed = line.trim() - if (trimmed == "InputBrake = KEY_S,JOY1_ZAXIS_POS") { + val repl = replacements[trimmed] + if (repl != null) { changed = true - "InputBrake = KEY_X,JOY1_ZAXIS_POS" + repl } else { line } diff --git a/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt b/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt index 07d7286..0428e4a 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt @@ -62,12 +62,27 @@ class Super3Activity : SDLActivity() { gamesXml.isNotBlank() && GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("vehicle", "harley")) + val hasShift4 = + game.isNotBlank() && + gamesXml.isNotBlank() && + GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("shift4")) + + val hasShiftUpDown = + game.isNotBlank() && + gamesXml.isNotBlank() && + GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("shiftupdown")) + + val showShifter = hasShift4 || hasShiftUpDown + overlay.findViewById(R.id.overlay_pedals)?.visibility = if (isRacing) View.VISIBLE else View.GONE overlay.findViewById(R.id.overlay_wheel)?.visibility = if (isRacing) View.VISIBLE else View.GONE + overlay.findViewById(R.id.overlay_shifter)?.visibility = + if (isRacing && showShifter) View.VISIBLE else View.GONE + fun nativeTouch(action: Int, fingerId: Int, x: Float, y: Float, p: Float = 1.0f) { SDLActivity.onNativeTouch(0, fingerId, action, x, y, p) } @@ -168,6 +183,52 @@ class Super3Activity : SDLActivity() { else -> true } } + + val shifter = overlay.findViewById(R.id.overlay_shifter) + shifter?.setOnTouchListener { v, ev -> + val w = v.width.toFloat().coerceAtLeast(1f) + val h = v.height.toFloat().coerceAtLeast(1f) + val cx = w / 2f + val cy = h / 2f + val dx = ((ev.x - cx) / cx).coerceIn(-1f, 1f) + val dy = ((ev.y - cy) / cy).coerceIn(-1f, 1f) + + // Provide a subtle "knob move" feel without changing layout. + v.translationX = dx * 10f + v.translationY = dy * 10f + + val encodedX = ((dx + 1f) / 2f).coerceIn(0f, 1f) + val encodedY = ((dy + 1f) / 2f).coerceIn(0f, 1f) + + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + v.alpha = 0.75f + nativeTouch(MotionEvent.ACTION_DOWN, 1108, encodedX, encodedY) + true + } + MotionEvent.ACTION_MOVE -> { + if (hasShift4) { + nativeTouch(MotionEvent.ACTION_MOVE, 1108, encodedX, encodedY) + } + true + } + MotionEvent.ACTION_UP -> { + v.alpha = 1.0f + v.translationX = 0f + v.translationY = 0f + nativeTouch(MotionEvent.ACTION_UP, 1108, 0.5f, 0.5f) + true + } + MotionEvent.ACTION_CANCEL -> { + v.alpha = 1.0f + v.translationX = 0f + v.translationY = 0f + nativeTouch(MotionEvent.ACTION_UP, 1108, 0.5f, 0.5f) + true + } + else -> true + } + } } } diff --git a/android/app/src/main/res/drawable/shifter.xml b/android/app/src/main/res/drawable/shifter.xml new file mode 100644 index 0000000..3a28528 --- /dev/null +++ b/android/app/src/main/res/drawable/shifter.xml @@ -0,0 +1,1826 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/overlay_controls.xml b/android/app/src/main/res/layout/overlay_controls.xml index 1c25b9c..2dd09cd 100644 --- a/android/app/src/main/res/layout/overlay_controls.xml +++ b/android/app/src/main/res/layout/overlay_controls.xml @@ -94,6 +94,21 @@ android:orientation="vertical" android:visibility="gone"> + + + +