better gun aiming some new settings options

This commit is contained in:
izzy2lost
2025-12-19 04:39:46 -05:00
parent e048fa7c73
commit 5f2fb09798
7 changed files with 205 additions and 34 deletions
@@ -57,6 +57,13 @@ void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled)
m_wheelFingerActive = false;
m_wheelFinger = 0;
m_virtualJoyX = 0;
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';
}
}
void AndroidInputSystem::SetVirtualShifterMode(bool shift4, bool shiftUpDown)
@@ -66,11 +73,33 @@ void AndroidInputSystem::SetVirtualShifterMode(bool shift4, bool shiftUpDown)
m_lastVirtualGear = -1;
}
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';
m_virtualJoyX = 0;
m_virtualJoyY = 0;
}
bool AndroidInputSystem::UseVirtualWheel() const
{
return m_virtualWheelEnabled && m_controllers.empty();
}
bool AndroidInputSystem::UseVirtualJoystick() const
{
return m_controllers.empty() && (m_virtualWheelEnabled || m_virtualAnalogGunEnabled);
}
void AndroidInputSystem::SetVirtualSteerFromEncoded(float encodedX)
{
// encodedX comes from Java as [(steer+1)/2], where steer is in [-1,1].
@@ -87,6 +116,14 @@ void AndroidInputSystem::SetVirtualSteerFromEncoded(float encodedX)
m_virtualJoyX = (int)std::lround(std::clamp(signedScaled, -1.0f, 1.0f) * 32767.0f);
}
void AndroidInputSystem::SetVirtualJoyFromNormalized(float x, float y)
{
const float sx = std::clamp((x - 0.5f) * 2.0f, -1.0f, 1.0f);
const float sy = std::clamp((y - 0.5f) * 2.0f, -1.0f, 1.0f);
m_virtualJoyX = (int)std::lround(sx * 32767.0f);
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 {
@@ -146,6 +183,7 @@ bool AndroidInputSystem::InitializeSystem()
m_wheelFingerActive = false;
m_wheelFinger = 0;
m_virtualJoyX = 0;
m_virtualJoyY = 0;
m_lastVirtualGear = -1;
SDL_GameControllerEventState(SDL_ENABLE);
@@ -372,6 +410,21 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
if (x > 0.75f && y < 0.25f) { PulseKeys(m_touchTest, 120); return; }
}
// Lightgun reload button: treat a dedicated synthetic fingerId as offscreen/reload,
// independent of the aiming touch.
if (m_gunTouchEnabled && tf.fingerId == 1109)
{
if (down)
{
// Most lightgun games treat "reload" as offscreen + trigger.
// If the player is already holding the trigger (aim finger active), only pulse offscreen.
PulseMouseButton(2, 140); // right = reload/offscreen
if (!m_gunFingerActive)
PulseMouseButton(0, 80); // left = trigger pulse (only when not already held)
}
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.
@@ -384,13 +437,10 @@ 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)
}
else
{
// Second touch while aiming: treat as offscreen/reload (right button pulse).
PulseMouseButton(2, 120);
}
}
else
{
@@ -509,6 +559,8 @@ 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;
}
@@ -556,6 +608,15 @@ int AndroidInputSystem::GetMouseAxisValue(int mseNum, int axisNum)
if (mseNum != ANY_MOUSE && mseNum != 0)
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))
{
const unsigned extent = (axisNum == AXIS_X) ? m_dispW : m_dispH;
const unsigned origin = (axisNum == AXIS_X) ? m_dispX : m_dispY;
return (int)(origin + extent / 2);
}
switch (axisNum)
{
case AXIS_X: return m_mouseX;
@@ -684,14 +745,14 @@ void AndroidInputSystem::RefreshControllers()
int AndroidInputSystem::GetNumJoysticks()
{
if (UseVirtualWheel())
if (UseVirtualJoystick())
return 1;
return static_cast<int>(m_controllers.size());
}
const JoyDetails* AndroidInputSystem::GetJoyDetails(int joyNum)
{
if (UseVirtualWheel())
if (UseVirtualJoystick())
{
if (joyNum == ANY_JOYSTICK || joyNum == 0)
return &m_virtualJoyDetails;
@@ -766,10 +827,12 @@ bool AndroidInputSystem::ButtonPressedFor(const ControllerState& c, int butNum)
int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
{
if (UseVirtualWheel())
if (UseVirtualJoystick())
{
if (axisNum == AXIS_X)
return m_virtualJoyX;
if (axisNum == AXIS_Y && m_virtualJoyDetails.hasAxis[AXIS_Y])
return m_virtualJoyY;
return 0;
}
@@ -23,6 +23,7 @@ public:
void SetGunTouchEnabled(bool enabled);
void SetVirtualWheelEnabled(bool enabled);
void SetVirtualShifterMode(bool shift4, bool shiftUpDown);
void SetVirtualAnalogGunEnabled(bool enabled);
// Called from the SDL event loop (main thread).
void HandleEvent(const SDL_Event& ev);
@@ -76,7 +77,9 @@ private:
void CloseControllers();
bool UseVirtualWheel() const;
bool UseVirtualJoystick() const;
void SetVirtualSteerFromEncoded(float encodedX);
void SetVirtualJoyFromNormalized(float x, float y);
void SetKey(SDL_Scancode sc, bool down);
void PulseKey(SDL_Scancode sc, uint32_t durationMs);
@@ -131,12 +134,15 @@ private:
SDL_FingerID m_wheelFinger = 0;
bool m_wheelFingerActive = false;
int m_virtualJoyX = 0;
int m_virtualJoyY = 0;
JoyDetails m_virtualJoyDetails{};
bool m_virtualShifterShift4 = false;
bool m_virtualShifterUpDown = false;
int m_lastVirtualGear = -1;
bool m_virtualAnalogGunEnabled = false;
MouseDetails m_mouseDetails{};
int m_mouseX = 0;
int m_mouseY = 0;
+3
View File
@@ -316,6 +316,9 @@ struct Super3Host {
(game.inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0;
inputSystem.SetGunTouchEnabled(gunGame);
const bool analogGunGame = (game.inputs & (Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0;
inputSystem.SetVirtualAnalogGunEnabled(analogGunGame);
const bool vehicleGame = (game.inputs & (Game::INPUT_VEHICLE | Game::INPUT_HARLEY)) != 0;
inputSystem.SetVirtualWheelEnabled(vehicleGame);
@@ -125,6 +125,8 @@ class MainActivity : AppCompatActivity() {
btnResolutionMatchDevice = headerView.findViewById(R.id.btn_resolution_match_device)
btnWidescreen = headerView.findViewById(R.id.btn_widescreen)
btnWideBackground = headerView.findViewById(R.id.btn_wide_background)
val btnShowTouchControls: MaterialButton = headerView.findViewById(R.id.btn_show_touch_controls)
val btnShowShifterOverlay: MaterialButton = headerView.findViewById(R.id.btn_show_shifter_overlay)
gamesAdapter = GamesAdapter { item ->
if (!item.launchable) {
@@ -146,6 +148,28 @@ class MainActivity : AppCompatActivity() {
bindVideoSettingsUi()
btnShowTouchControls.isChecked = prefs.getBoolean("overlay_controls_enabled", true)
btnShowTouchControls.setOnClickListener {
val enabled = btnShowTouchControls.isChecked
prefs.edit().putBoolean("overlay_controls_enabled", enabled).apply()
Toast.makeText(
this,
if (enabled) "Touch controls enabled" else "Touch controls hidden",
Toast.LENGTH_SHORT,
).show()
}
btnShowShifterOverlay.isChecked = prefs.getBoolean("overlay_shifter_enabled", false)
btnShowShifterOverlay.setOnClickListener {
val enabled = btnShowShifterOverlay.isChecked
prefs.edit().putBoolean("overlay_shifter_enabled", enabled).apply()
Toast.makeText(
this,
if (enabled) "Shifter overlay enabled" else "Shifter overlay disabled",
Toast.LENGTH_SHORT,
).show()
}
runCatching { searchView.setupWithSearchBar(searchBar) }
searchBar.setOnClickListener {
searchView.show()
@@ -9,6 +9,7 @@ import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.RelativeLayout
import com.google.android.material.button.MaterialButton
import java.io.File
import kotlin.concurrent.thread
import org.libsdl.app.SDLActivity
@@ -42,6 +43,10 @@ class Super3Activity : SDLActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val overlaysEnabled = getSharedPreferences("super3_prefs", MODE_PRIVATE)
.getBoolean("overlay_controls_enabled", true)
if (!overlaysEnabled) return
val root = SDLActivity.getContentView() as? RelativeLayout ?: return
if (overlayView != null) return
@@ -72,7 +77,14 @@ class Super3Activity : SDLActivity() {
gamesXml.isNotBlank() &&
GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("shiftupdown"))
val showShifter = hasShift4 || hasShiftUpDown
val isGunGame =
game.isNotBlank() &&
gamesXml.isNotBlank() &&
GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("gun1", "gun2", "analog_gun1", "analog_gun2"))
val shifterEnabled = getSharedPreferences("super3_prefs", MODE_PRIVATE)
.getBoolean("overlay_shifter_enabled", false)
val showShifter = shifterEnabled && (hasShift4 || hasShiftUpDown)
overlay.findViewById<LinearLayout>(R.id.overlay_pedals)?.visibility =
if (isRacing) View.VISIBLE else View.GONE
@@ -83,6 +95,9 @@ class Super3Activity : SDLActivity() {
overlay.findViewById<ImageButton>(R.id.overlay_shifter)?.visibility =
if (isRacing && showShifter) View.VISIBLE else View.GONE
overlay.findViewById<MaterialButton>(R.id.overlay_reload)?.visibility =
if (isGunGame) 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)
}
@@ -140,6 +155,9 @@ class Super3Activity : SDLActivity() {
bindMomentary(R.id.overlay_start, fingerId = 1102, x = 0.50f, y = 0.90f)
bindMomentary(R.id.overlay_service, fingerId = 1105, x = 0.10f, y = 0.10f)
bindMomentary(R.id.overlay_test, fingerId = 1106, x = 0.90f, y = 0.10f)
if (isGunGame) {
bindMomentary(R.id.overlay_reload, fingerId = 1109, x = 0.90f, y = 0.90f)
}
if (isRacing) {
// Match the native pedal zone (right-middle), independent of UI placement.
@@ -185,7 +203,8 @@ class Super3Activity : SDLActivity() {
}
val shifter = overlay.findViewById<ImageButton>(R.id.overlay_shifter)
shifter?.setOnTouchListener { v, ev ->
if (showShifter) {
shifter?.setOnTouchListener { v, ev ->
val w = v.width.toFloat().coerceAtLeast(1f)
val h = v.height.toFloat().coerceAtLeast(1f)
val cx = w / 2f
@@ -229,6 +248,9 @@ class Super3Activity : SDLActivity() {
else -> true
}
}
} else {
shifter?.setOnTouchListener(null)
}
}
}
@@ -101,6 +101,33 @@
android:text="Wide background"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/controls_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Controls"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorOnPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_show_touch_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checkable="true"
android:text="Show touch controls"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_show_shifter_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checkable="true"
android:text="Show shifter overlay"
style="?attr/materialButtonElevatedStyle" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/games_folder_text"
android:layout_width="match_parent"
@@ -83,9 +83,27 @@
app:rippleColor="@color/overlay_ripple"
app:strokeWidth="2dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/overlay_reload"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:minHeight="0dp"
android:minWidth="0dp"
android:paddingHorizontal="14dp"
android:paddingVertical="10dp"
android:text="RELOAD"
android:textAllCaps="true"
android:textSize="12sp"
android:visibility="gone"
app:rippleColor="@color/overlay_ripple"
app:strokeWidth="2dp" />
<LinearLayout
android:id="@+id/overlay_pedals"
android:layout_width="92dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
@@ -96,8 +114,9 @@
<ImageButton
android:id="@+id/overlay_shifter"
android:layout_width="match_parent"
android:layout_width="92dp"
android:layout_height="120dp"
android:layout_gravity="end"
android:background="@drawable/overlay_ripple_rounded"
android:contentDescription="Shifter"
android:padding="8dp"
@@ -107,31 +126,38 @@
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />
android:layout_height="6dp" />
<ImageButton
android:id="@+id/overlay_gas"
android:layout_width="match_parent"
android:layout_height="92dp"
android:background="@drawable/overlay_ripple_rounded"
android:contentDescription="Gas"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@drawable/gaspedal" />
<LinearLayout
android:id="@+id/overlay_pedal_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />
<ImageButton
android:id="@+id/overlay_brake"
android:layout_width="92dp"
android:layout_height="92dp"
android:background="@drawable/overlay_ripple_rounded"
android:contentDescription="Brake"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@drawable/brakepedal" />
<ImageButton
android:id="@+id/overlay_brake"
android:layout_width="match_parent"
android:layout_height="92dp"
android:background="@drawable/overlay_ripple_rounded"
android:contentDescription="Brake"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@drawable/brakepedal" />
<Space
android:layout_width="10dp"
android:layout_height="match_parent" />
<ImageButton
android:id="@+id/overlay_gas"
android:layout_width="92dp"
android:layout_height="92dp"
android:background="@drawable/overlay_ripple_rounded"
android:contentDescription="Gas"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@drawable/gaspedal" />
</LinearLayout>
</LinearLayout>
</FrameLayout>