mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
better gun aiming some new settings options
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user