diff --git a/android/app/src/main/assets/Config/Supermodel.ini b/android/app/src/main/assets/Config/Supermodel.ini index 19c0017..cf46750 100644 --- a/android/app/src/main/assets/Config/Supermodel.ini +++ b/android/app/src/main/assets/Config/Supermodel.ini @@ -121,20 +121,20 @@ InputAnalogJoyLeft = KEY_LEFT InputAnalogJoyRight = KEY_RIGHT InputAnalogJoyUp = KEY_UP InputAnalogJoyDown = KEY_DOWN -InputAnalogJoyX = JOY_XAXIS,MOUSE_XAXIS -InputAnalogJoyY = JOY_YAXIS,MOUSE_YAXIS -InputAnalogJoyTrigger = KEY_A,JOY_BUTTON1,MOUSE_LEFT_BUTTON -InputAnalogJoyEvent = KEY_S,JOY_BUTTON2,MOUSE_RIGHT_BUTTON +InputAnalogJoyX = JOY_XAXIS,MOUSE_XAXIS_INV +InputAnalogJoyY = JOY_YAXIS,MOUSE_YAXIS_INV +InputAnalogJoyTrigger = KEY_A,KEY_W,JOY1_BUTTON1,MOUSE_LEFT_BUTTON +InputAnalogJoyEvent = KEY_S,KEY_X,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON InputAnalogJoyTrigger2 = KEY_D,JOY_BUTTON2 InputAnalogJoyEvent2 = NONE InputGunLeft = KEY_LEFT InputGunRight = KEY_RIGHT InputGunUp = KEY_UP InputGunDown = KEY_DOWN -InputGunX = MOUSE_XAXIS,JOY1_XAXIS -InputGunY = MOUSE_YAXIS,JOY1_YAXIS -InputTrigger = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON -InputOffscreen = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON +InputGunX = MOUSE_XAXIS,JOY1_RXAXIS,JOY1_XAXIS +InputGunY = MOUSE_YAXIS,JOY1_RYAXIS,JOY1_YAXIS +InputTrigger = KEY_A,JOY1_RZAXIS_POS,JOY1_BUTTON1,MOUSE_LEFT_BUTTON +InputOffscreen = KEY_S,JOY1_ZAXIS_POS,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON InputAutoTrigger = 1 InputGunLeft2 = NONE InputGunRight2 = NONE @@ -149,18 +149,18 @@ InputAnalogGunLeft = KEY_LEFT InputAnalogGunRight = KEY_RIGHT InputAnalogGunUp = KEY_UP InputAnalogGunDown = KEY_DOWN -InputAnalogGunX = MOUSE_XAXIS,JOY1_XAXIS -InputAnalogGunY = MOUSE_YAXIS,JOY1_YAXIS -InputAnalogTriggerLeft = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON -InputAnalogTriggerRight = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON +InputAnalogGunX = MOUSE_XAXIS,JOY1_RXAXIS,JOY1_XAXIS +InputAnalogGunY = MOUSE_YAXIS,JOY1_RYAXIS,JOY1_YAXIS +InputAnalogTriggerLeft = KEY_A,JOY1_RZAXIS_POS,JOY1_BUTTON1,MOUSE_LEFT_BUTTON +InputAnalogTriggerRight = KEY_S,JOY1_ZAXIS_POS,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON InputAnalogGunLeft2 = NONE InputAnalogGunRight2 = NONE InputAnalogGunUp2 = NONE InputAnalogGunDown2 = NONE -InputAnalogGunX2 = NONE -InputAnalogGunY2 = NONE -InputAnalogTriggerLeft2 = NONE -InputAnalogTriggerRight2 = NONE +InputAnalogGunX2 = JOY2_RXAXIS,JOY2_XAXIS +InputAnalogGunY2 = JOY2_RYAXIS,JOY2_YAXIS +InputAnalogTriggerLeft2 = JOY2_RZAXIS_POS,JOY2_BUTTON1 +InputAnalogTriggerRight2 = JOY2_ZAXIS_POS,JOY2_BUTTON2 InputSkiLeft = KEY_LEFT InputSkiRight = KEY_RIGHT InputSkiUp = KEY_UP @@ -197,4 +197,10 @@ InputFishingCast = KEY_Z,JOY1_BUTTON1 InputFishingSelect = KEY_X,JOY1_BUTTON2 InputFishingTension = KEY_T,JOY1_ZAXIS_NEG +[ lamachin ] +InputAnalogGunLeft = JOY1_XAXIS_NEG +InputAnalogGunRight = JOY1_XAXIS_POS +InputAnalogGunUp = JOY1_YAXIS_NEG +InputAnalogGunDown = JOY1_YAXIS_POS + diff --git a/android/app/src/main/cpp/android_input_system.cpp b/android/app/src/main/cpp/android_input_system.cpp index cd83d31..6a088ae 100644 --- a/android/app/src/main/cpp/android_input_system.cpp +++ b/android/app/src/main/cpp/android_input_system.cpp @@ -14,7 +14,7 @@ AndroidInputSystem::AndroidInputSystem() m_mouseDetails.isAbsolute = true; std::memset(&m_virtualJoyDetails, 0, sizeof(m_virtualJoyDetails)); - std::strncpy(m_virtualJoyDetails.name, "Touch Wheel", MAX_NAME_LENGTH); + std::strncpy(m_virtualJoyDetails.name, "Touch Controls", MAX_NAME_LENGTH); m_virtualJoyDetails.name[MAX_NAME_LENGTH] = '\0'; m_virtualJoyDetails.numAxes = 6; m_virtualJoyDetails.numPOVs = 0; @@ -58,12 +58,7 @@ void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled) m_wheelFinger = 0; m_virtualJoyX.store(0, std::memory_order_relaxed); m_virtualJoyY = 0; - if (enabled && !m_virtualAnalogGunEnabled) - { - m_virtualJoyDetails.hasAxis[AXIS_Y] = false; - std::strncpy(m_virtualJoyDetails.name, "Touch Wheel", MAX_NAME_LENGTH); - m_virtualJoyDetails.name[MAX_NAME_LENGTH] = '\0'; - } + UpdateVirtualJoystickDescriptor(); } void AndroidInputSystem::SetVirtualShifterMode(bool shift4, bool shiftUpDown) @@ -78,14 +73,17 @@ void AndroidInputSystem::SetVirtualAnalogGunEnabled(bool enabled) if (m_virtualAnalogGunEnabled == enabled) return; m_virtualAnalogGunEnabled = enabled; - m_virtualJoyDetails.hasAxis[AXIS_Y] = enabled; - if (enabled && !m_virtualWheelEnabled) - std::strncpy(m_virtualJoyDetails.name, "Touch Gun", MAX_NAME_LENGTH); - else if (!enabled && m_virtualWheelEnabled) - std::strncpy(m_virtualJoyDetails.name, "Touch Wheel", MAX_NAME_LENGTH); - else - std::strncpy(m_virtualJoyDetails.name, "Touch Controls", MAX_NAME_LENGTH); - m_virtualJoyDetails.name[MAX_NAME_LENGTH] = '\0'; + UpdateVirtualJoystickDescriptor(); + m_virtualJoyX.store(0, std::memory_order_relaxed); + m_virtualJoyY = 0; +} + +void AndroidInputSystem::SetVirtualAnalogJoystickEnabled(bool enabled) +{ + if (m_virtualAnalogJoystickEnabled == enabled) + return; + m_virtualAnalogJoystickEnabled = enabled; + UpdateVirtualJoystickDescriptor(); m_virtualJoyX.store(0, std::memory_order_relaxed); m_virtualJoyY = 0; } @@ -97,7 +95,7 @@ bool AndroidInputSystem::UseVirtualWheel() const bool AndroidInputSystem::UseVirtualJoystick() const { - return m_controllers.empty() && (m_virtualWheelEnabled || m_virtualAnalogGunEnabled); + return m_controllers.empty() && (m_virtualWheelEnabled || m_virtualAnalogGunEnabled || m_virtualAnalogJoystickEnabled); } void AndroidInputSystem::SetVirtualSteerFromEncoded(float encodedX) @@ -106,6 +104,27 @@ void AndroidInputSystem::SetVirtualSteerFromEncoded(float encodedX) SetVirtualSteerFromSteer((encodedX - 0.5f) * 2.0f); } +void AndroidInputSystem::UpdateVirtualJoystickDescriptor() +{ + const bool wheel = m_virtualWheelEnabled; + const bool gun = m_virtualAnalogGunEnabled; + const bool stick = m_virtualAnalogJoystickEnabled; + const int enabledCount = (wheel ? 1 : 0) + (gun ? 1 : 0) + (stick ? 1 : 0); + + m_virtualJoyDetails.hasAxis[AXIS_Y] = gun || stick; + + const char* name = "Touch Controls"; + if (enabledCount <= 1) + { + if (wheel) name = "Touch Wheel"; + else if (gun) name = "Touch Gun"; + else if (stick) name = "Touch Stick"; + } + + std::strncpy(m_virtualJoyDetails.name, name, MAX_NAME_LENGTH); + m_virtualJoyDetails.name[MAX_NAME_LENGTH] = '\0'; +} + void AndroidInputSystem::SetVirtualSteerFromSteer(float steer) { steer = std::clamp(steer, -1.0f, 1.0f); @@ -137,6 +156,33 @@ void AndroidInputSystem::SetVirtualJoyFromNormalized(float x, float y) m_virtualJoyY = (int)std::lround(sy * 32767.0f); } +void AndroidInputSystem::SetVirtualStickFromNormalized(float x, float y) +{ + float sx = std::clamp((x - 0.5f) * 2.0f, -1.0f, 1.0f); + float sy = std::clamp((y - 0.5f) * 2.0f, -1.0f, 1.0f); + + // Apply a circular deadzone + a mild curve so the center is less twitchy. + const float mag = std::sqrt(sx * sx + sy * sy); + constexpr float deadzone = 0.18f; + if (mag < deadzone) + { + m_virtualJoyX.store(0, std::memory_order_relaxed); + m_virtualJoyY = 0; + return; + } + + float scaled = (mag - deadzone) / (1.0f - deadzone); + scaled = std::clamp(scaled, 0.0f, 1.0f); + scaled *= scaled; + + const float invMag = 1.0f / mag; + sx = sx * invMag * scaled; + sy = sy * invMag * scaled; + + m_virtualJoyX.store((int)std::lround(sx * 32767.0f), std::memory_order_relaxed); + m_virtualJoyY = (int)std::lround(sy * 32767.0f); +} + void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config) { auto get = [&](const char* key, const char* fallback) -> std::string { @@ -460,6 +506,15 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) // Virtual fighting stick: 8-way directional based on encoded x/y in [0..1], keyed by a fixed fingerId. if (tf.fingerId == 1114) { + if (m_virtualAnalogJoystickEnabled || m_virtualAnalogGunEnabled) + { + if (down) + SetVirtualStickFromNormalized(x, y); + else + SetVirtualStickFromNormalized(0.5f, 0.5f); + return; + } + auto releaseHeld = [&]() { auto it = m_fingerHeldDir.find(tf.fingerId); if (it != m_fingerHeldDir.end()) @@ -648,8 +703,6 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) m_gunFingerActive = true; m_gunFinger = tf.fingerId; SetMousePosFromNormalized(x, y); - if (m_virtualAnalogGunEnabled) - SetVirtualJoyFromNormalized(x, y); SetMouseButton(0, true); // left = trigger (held) } } @@ -779,8 +832,6 @@ void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf) if (m_gunFingerActive && tf.fingerId == m_gunFinger) { SetMousePosFromNormalized(tf.x, tf.y); - if (m_virtualAnalogGunEnabled) - SetVirtualJoyFromNormalized(tf.x, tf.y); } return; } @@ -830,8 +881,11 @@ int AndroidInputSystem::GetMouseAxisValue(int mseNum, int axisNum) return 0; // For analog-gun games, prefer the virtual joystick (JOY1_XAXIS/JOY1_YAXIS). - // Keep the virtual mouse centered so MOUSE_XAXIS/MOUSE_YAXIS mappings don't override JOY mappings. - if (m_virtualAnalogGunEnabled && (axisNum == AXIS_X || axisNum == AXIS_Y)) + // Keep the virtual mouse centered unless the user is actively aiming via touch. + // This lets: + // - touch aim drive MOUSE_* mappings while held, and + // - physical controller aim drive JOY* mappings when not touching. + if (m_virtualAnalogGunEnabled && (axisNum == AXIS_X || axisNum == AXIS_Y) && !m_gunFingerActive) { const unsigned extent = (axisNum == AXIS_X) ? m_dispW : m_dispH; const unsigned origin = (axisNum == AXIS_X) ? m_dispX : m_dispY; diff --git a/android/app/src/main/cpp/android_input_system.h b/android/app/src/main/cpp/android_input_system.h index 51a1b85..d318777 100644 --- a/android/app/src/main/cpp/android_input_system.h +++ b/android/app/src/main/cpp/android_input_system.h @@ -25,6 +25,7 @@ public: void SetVirtualWheelEnabled(bool enabled); void SetVirtualShifterMode(bool shift4, bool shiftUpDown); void SetVirtualAnalogGunEnabled(bool enabled); + void SetVirtualAnalogJoystickEnabled(bool enabled); void SetGyroSteer(float steer); // [-1..1] // Called from the SDL event loop (main thread). @@ -88,6 +89,8 @@ private: void SetVirtualSteerFromEncoded(float encodedX); void SetVirtualSteerFromSteer(float steer); void SetVirtualJoyFromNormalized(float x, float y); + void SetVirtualStickFromNormalized(float x, float y); + void UpdateVirtualJoystickDescriptor(); void SetKey(SDL_Scancode sc, bool down); void PulseKey(SDL_Scancode sc, uint32_t durationMs); @@ -186,6 +189,7 @@ private: int m_lastVirtualGear = -1; bool m_virtualAnalogGunEnabled = false; + bool m_virtualAnalogJoystickEnabled = false; MouseDetails m_mouseDetails{}; int m_mouseX = 0; diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index 8706f2e..c70321c 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -463,7 +463,8 @@ struct Super3Host { const bool gunGame = (game.inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0; - inputSystem.SetGunTouchEnabled(gunGame); + const bool analogJoystickGame = (game.inputs & Game::INPUT_ANALOG_JOYSTICK) != 0; + inputSystem.SetGunTouchEnabled(gunGame || analogJoystickGame); const bool analogGunGame = (game.inputs & (Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0; inputSystem.SetVirtualAnalogGunEnabled(analogGunGame); @@ -471,6 +472,8 @@ struct Super3Host { const bool vehicleGame = (game.inputs & (Game::INPUT_VEHICLE | Game::INPUT_HARLEY)) != 0; inputSystem.SetVirtualWheelEnabled(vehicleGame); + inputSystem.SetVirtualAnalogJoystickEnabled(analogJoystickGame); + const bool shift4 = (game.inputs & Game::INPUT_SHIFT4) != 0; const bool shiftUpDown = (game.inputs & Game::INPUT_SHIFTUPDOWN) != 0; inputSystem.SetVirtualShifterMode(shift4, shiftUpDown); 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 ac73646..668f970 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt @@ -57,6 +57,25 @@ object AssetInstaller { "InputGearShiftN = KEY_T" to "InputGearShiftN = KEY_6", "InputAutoTrigger = 0" to "InputAutoTrigger = 1", "InputAutoTrigger2 = 0" to "InputAutoTrigger2 = 1", + // Star Wars Trilogy: correct mouse inversion + allow using the pedal touch zone as Trigger/Event. + "InputAnalogJoyX = JOY_XAXIS,MOUSE_XAXIS" to "InputAnalogJoyX = JOY_XAXIS,MOUSE_XAXIS_INV", + "InputAnalogJoyY = JOY_YAXIS,MOUSE_YAXIS" to "InputAnalogJoyY = JOY_YAXIS,MOUSE_YAXIS_INV", + "InputAnalogJoyTrigger = KEY_A,JOY_BUTTON1,MOUSE_LEFT_BUTTON" to "InputAnalogJoyTrigger = KEY_A,KEY_W,JOY1_BUTTON1,MOUSE_LEFT_BUTTON", + "InputAnalogJoyEvent = KEY_S,JOY_BUTTON2,MOUSE_RIGHT_BUTTON" to "InputAnalogJoyEvent = KEY_S,KEY_X,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON", + // Gun games: include right stick + trigger axes in default mappings. + "InputGunX = MOUSE_XAXIS,JOY1_XAXIS" to "InputGunX = MOUSE_XAXIS,JOY1_RXAXIS,JOY1_XAXIS", + "InputGunY = MOUSE_YAXIS,JOY1_YAXIS" to "InputGunY = MOUSE_YAXIS,JOY1_RYAXIS,JOY1_YAXIS", + "InputTrigger = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON" to "InputTrigger = KEY_A,JOY1_RZAXIS_POS,JOY1_BUTTON1,MOUSE_LEFT_BUTTON", + "InputOffscreen = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON" to "InputOffscreen = KEY_S,JOY1_ZAXIS_POS,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON", + // Analog-gun games: include right stick + trigger axes, and set P2 defaults if still unmapped. + "InputAnalogGunX = MOUSE_XAXIS,JOY1_XAXIS" to "InputAnalogGunX = MOUSE_XAXIS,JOY1_RXAXIS,JOY1_XAXIS", + "InputAnalogGunY = MOUSE_YAXIS,JOY1_YAXIS" to "InputAnalogGunY = MOUSE_YAXIS,JOY1_RYAXIS,JOY1_YAXIS", + "InputAnalogTriggerLeft = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON" to "InputAnalogTriggerLeft = KEY_A,JOY1_RZAXIS_POS,JOY1_BUTTON1,MOUSE_LEFT_BUTTON", + "InputAnalogTriggerRight = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON" to "InputAnalogTriggerRight = KEY_S,JOY1_ZAXIS_POS,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON", + "InputAnalogGunX2 = NONE" to "InputAnalogGunX2 = JOY2_RXAXIS,JOY2_XAXIS", + "InputAnalogGunY2 = NONE" to "InputAnalogGunY2 = JOY2_RYAXIS,JOY2_YAXIS", + "InputAnalogTriggerLeft2 = NONE" to "InputAnalogTriggerLeft2 = JOY2_RZAXIS_POS,JOY2_BUTTON1", + "InputAnalogTriggerRight2 = NONE" to "InputAnalogTriggerRight2 = JOY2_ZAXIS_POS,JOY2_BUTTON2", ) val updated = lines.map { 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 25ebf68..336b0e5 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/Super3Activity.kt @@ -212,8 +212,17 @@ class Super3Activity : SDLActivity() { gamesXml.isNotBlank() && GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("spikeout")) + val needsAimStickAndButtons = + game.isNotBlank() && + gamesXml.isNotBlank() && + ( + game == "lamachin" || + GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("analog_joystick")) + ) + val showFightButtons = isFighting || isSpikeout || isSoccer || isTwinJoysticks || isFishing || isSki - val showFightStick = showFightButtons || isMagTruck + val showFightStick = showFightButtons || isMagTruck || needsAimStickAndButtons + val showExtraButtons = showFightButtons || needsAimStickAndButtons val shifterEnabled = getSharedPreferences("super3_prefs", MODE_PRIVATE) .getBoolean("overlay_shifter_enabled", false) @@ -238,7 +247,7 @@ class Super3Activity : SDLActivity() { if (showFightStick) View.VISIBLE else View.GONE overlay.findViewById(R.id.overlay_fight_buttons)?.visibility = - if (showFightButtons) View.VISIBLE else View.GONE + if (showExtraButtons) View.VISIBLE else View.GONE overlay.findViewById(R.id.overlay_vo_row)?.visibility = if (isTwinJoysticks) View.VISIBLE else View.GONE @@ -335,7 +344,7 @@ class Super3Activity : SDLActivity() { bindMomentary(R.id.overlay_reload, fingerId = 1109, x = 0.90f, y = 0.90f) } - if (showFightButtons) { + if (showExtraButtons) { val baseId = if (isSpikeout) { 1115