Added manual shifter to osd

This commit is contained in:
izzy2lost
2025-12-19 02:58:16 -05:00
parent 2435651cbd
commit e048fa7c73
8 changed files with 2039 additions and 9 deletions
@@ -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
@@ -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)
@@ -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;
+4
View File
@@ -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();
@@ -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
}
@@ -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<LinearLayout>(R.id.overlay_pedals)?.visibility =
if (isRacing) View.VISIBLE else View.GONE
overlay.findViewById<ImageButton>(R.id.overlay_wheel)?.visibility =
if (isRacing) View.VISIBLE else View.GONE
overlay.findViewById<ImageButton>(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<ImageButton>(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
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -94,6 +94,21 @@
android:orientation="vertical"
android:visibility="gone">
<ImageButton
android:id="@+id/overlay_shifter"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@drawable/overlay_ripple_rounded"
android:contentDescription="Shifter"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@drawable/shifter"
android:visibility="gone" />
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />
<ImageButton
android:id="@+id/overlay_gas"
android:layout_width="match_parent"