added mouse pad and crosshairs button for gun games

This commit is contained in:
izzy2lost
2026-01-28 08:53:08 -05:00
parent 6bb81e74ea
commit 7de85d6e46
9 changed files with 538 additions and 11 deletions
@@ -51,6 +51,7 @@ InputCoin1 = KEY_5,JOY1_BUTTON7
InputStart1 = KEY_1,JOY1_BUTTON8
InputServiceA = KEY_F1,JOY1_BUTTON13
InputTestA = KEY_F2
InputUISelectCrosshairs = KEY_F8
InputJoyUp = KEY_UP,JOY1_POV1_UP,JOY1_YAXIS_NEG
InputJoyDown = KEY_DOWN,JOY1_POV1_DOWN,JOY1_YAXIS_POS
InputJoyLeft = KEY_LEFT,JOY1_POV1_LEFT,JOY1_XAXIS_NEG
@@ -42,6 +42,10 @@ void AndroidInputSystem::SetGunTouchEnabled(bool enabled)
m_gunTouchEnabled = enabled;
m_gunFingerActive = false;
m_gunFinger = 0;
m_mousePadActive = false;
m_mousePadFinger = 0;
m_mousePadLastX = 0.0f;
m_mousePadLastY = 0.0f;
m_mouseX = 0;
m_mouseY = 0;
m_mouseWheelDir = 0;
@@ -49,6 +53,16 @@ void AndroidInputSystem::SetGunTouchEnabled(bool enabled)
std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs));
}
void AndroidInputSystem::SetGunStickAimEnabled(bool enabled)
{
if (m_gunStickAimEnabled == enabled)
return;
m_gunStickAimEnabled = enabled;
m_lastGunAimTickMs = 0;
if (m_gunStickAimEnabled)
CenterMouse();
}
void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled)
{
if (m_virtualWheelEnabled == enabled)
@@ -270,6 +284,8 @@ void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config)
m_touchSkiPollRight.a = keySc(get("InputSkiPollRight", "KEY_S"), SDL_SCANCODE_S);
m_touchSkiSelect1.a = keySc(get("InputSkiSelect1", "KEY_Q"), SDL_SCANCODE_Q);
m_touchSkiSelect2.a = keySc(get("InputSkiSelect2", "KEY_W"), SDL_SCANCODE_W);
m_touchCrosshairs.a = keySc(get("InputUISelectCrosshairs", "KEY_F8"), SDL_SCANCODE_F8);
}
void AndroidInputSystem::EnsureGameControllerMappingsLoaded()
@@ -307,8 +323,13 @@ bool AndroidInputSystem::InitializeSystem()
std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs));
m_gunFingerActive = false;
m_gunFinger = 0;
m_mousePadActive = false;
m_mousePadFinger = 0;
m_mousePadLastX = 0.0f;
m_mousePadLastY = 0.0f;
m_suppressGunTriggerUntilMs = 0;
m_pendingReloadTriggerAtMs = 0;
m_lastGunAimTickMs = 0;
m_wheelFingerActive = false;
m_wheelFinger = 0;
m_virtualJoyX.store(0, std::memory_order_relaxed);
@@ -354,6 +375,7 @@ bool AndroidInputSystem::Poll()
SDL_JoystickUpdate();
const uint32_t now = SDL_GetTicks();
UpdateGunStickAim(now);
if (m_pendingReloadTriggerAtMs != 0 && now >= m_pendingReloadTriggerAtMs)
{
// Manual reload helper when the core's InputAutoTrigger is disabled:
@@ -503,9 +525,36 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
case 1141: SetKeys(m_touchSkiPollRight, down); return;
case 1142: SetKeys(m_touchSkiSelect1, down); return;
case 1143: SetKeys(m_touchSkiSelect2, down); return;
case 1119: // Crosshairs toggle
if (down) PulseKeys(m_touchCrosshairs, 120);
return;
default: break;
}
// Virtual mouse pad (relative mouse movement).
if (tf.fingerId == 1125)
{
if (down)
{
if (!m_mousePadActive)
{
m_mousePadActive = true;
m_mousePadFinger = tf.fingerId;
m_mousePadLastX = tf.x;
m_mousePadLastY = tf.y;
}
}
else
{
if (m_mousePadActive && tf.fingerId == m_mousePadFinger)
{
m_mousePadActive = false;
m_mousePadFinger = 0;
}
}
return;
}
// Virtual fighting stick: 8-way directional based on encoded x/y in [0..1], keyed by a fixed fingerId.
if (tf.fingerId == 1114)
{
@@ -790,6 +839,30 @@ void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf)
return;
}
if (m_mousePadActive && tf.fingerId == m_mousePadFinger)
{
const float dx = tf.x - m_mousePadLastX;
const float dy = tf.y - m_mousePadLastY;
m_mousePadLastX = tf.x;
m_mousePadLastY = tf.y;
if (dx == 0.0f && dy == 0.0f)
return;
const int w = (m_dispW > 0) ? (int)m_dispW : 496;
const int h = (m_dispH > 0) ? (int)m_dispH : 384;
const int x0 = (int)m_dispX;
const int y0 = (int)m_dispY;
constexpr float sensitivity = 1.0f;
m_mouseX += (int)std::lround(dx * (float)w * sensitivity);
m_mouseY += (int)std::lround(dy * (float)h * sensitivity);
const int maxX = x0 + w - 1;
const int maxY = y0 + h - 1;
m_mouseX = std::clamp(m_mouseX, x0, maxX);
m_mouseY = std::clamp(m_mouseY, y0, maxY);
return;
}
if (m_virtualShifterShift4 && tf.fingerId == 1108)
{
// Avoid accidental neutral when passing through center during motion.
@@ -888,7 +961,7 @@ int AndroidInputSystem::GetMouseAxisValue(int mseNum, int axisNum)
// 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)
if (m_virtualAnalogGunEnabled && (axisNum == AXIS_X || axisNum == AXIS_Y) && !m_gunFingerActive && !m_gunStickAimEnabled)
{
const unsigned extent = (axisNum == AXIS_X) ? m_dispW : m_dispH;
const unsigned origin = (axisNum == AXIS_X) ? m_dispX : m_dispY;
@@ -959,6 +1032,93 @@ void AndroidInputSystem::SetMousePosFromNormalized(float x, float y)
m_mouseY = std::clamp(py, 0, h - 1);
}
void AndroidInputSystem::CenterMouse()
{
const int w = (m_dispW > 0) ? (int)m_dispW : 496;
const int h = (m_dispH > 0) ? (int)m_dispH : 384;
const int x0 = (int)m_dispX;
const int y0 = (int)m_dispY;
m_mouseX = x0 + w / 2;
m_mouseY = y0 + h / 2;
}
void AndroidInputSystem::UpdateGunStickAim(uint32_t nowMs)
{
if (!m_gunStickAimEnabled || m_controllers.empty() || m_gunFingerActive || m_mousePadActive)
{
m_lastGunAimTickMs = nowMs;
return;
}
float dt = 0.0f;
if (m_lastGunAimTickMs != 0)
{
const uint32_t delta = nowMs - m_lastGunAimTickMs;
dt = (float)delta / 1000.0f;
if (dt > 0.05f)
dt = 0.05f;
}
m_lastGunAimTickMs = nowMs;
if (dt <= 0.0f)
return;
auto normAxis = [](int v) {
if (v <= -32768) return -1.0f;
if (v >= 32767) return 1.0f;
return (float)v / 32767.0f;
};
auto axisAny = [&](int axisNum) {
int best = 0;
for (const auto& c : m_controllers)
{
const int v = AxisValueFor(c, axisNum);
if (std::abs(v) > std::abs(best))
best = v;
}
return best;
};
int ax = axisAny(AXIS_RX);
int ay = axisAny(AXIS_RY);
float fx = normAxis(ax);
float fy = normAxis(ay);
constexpr float deadzone = 0.18f;
float mag = std::sqrt(fx * fx + fy * fy);
if (mag < deadzone)
{
ax = axisAny(AXIS_X);
ay = axisAny(AXIS_Y);
fx = normAxis(ax);
fy = normAxis(ay);
mag = std::sqrt(fx * fx + fy * fy);
}
if (mag < deadzone)
return;
float scaled = (mag - deadzone) / (1.0f - deadzone);
scaled = std::clamp(scaled, 0.0f, 1.0f);
const float invMag = 1.0f / mag;
fx = fx * invMag * scaled;
fy = fy * invMag * scaled;
const int w = (m_dispW > 0) ? (int)m_dispW : 496;
const int h = (m_dispH > 0) ? (int)m_dispH : 384;
const int x0 = (int)m_dispX;
const int y0 = (int)m_dispY;
const float baseSpeed = (float)std::max(w, h) * 1.6f;
m_mouseX += (int)std::lround(fx * baseSpeed * dt);
m_mouseY += (int)std::lround(fy * baseSpeed * dt);
const int maxX = x0 + w - 1;
const int maxY = y0 + h - 1;
m_mouseX = std::clamp(m_mouseX, x0, maxX);
m_mouseY = std::clamp(m_mouseY, y0, maxY);
}
void AndroidInputSystem::CloseControllers()
{
for (auto& c : m_controllers)
@@ -1237,6 +1397,20 @@ bool AndroidInputSystem::ButtonPressedFor(const ControllerState& c, int butNum)
int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
{
if (m_gunStickAimEnabled && m_virtualAnalogJoystickEnabled && !UseVirtualJoystick() &&
(axisNum == AXIS_X || axisNum == AXIS_Y || axisNum == AXIS_RX || axisNum == AXIS_RY))
{
return 0;
}
// Analog-gun games often map the gun axis to JOY1_X/Y, which can override our
// mouse/touch/stick-aim path. Suppress raw joystick axes so the gun uses MOUSE_*.
if (m_gunStickAimEnabled && m_virtualAnalogGunEnabled && !UseVirtualJoystick() &&
(axisNum == AXIS_X || axisNum == AXIS_Y || axisNum == AXIS_RX || axisNum == AXIS_RY))
{
return 0;
}
if (UseVirtualJoystick())
{
if (axisNum == AXIS_X)
@@ -22,6 +22,7 @@ public:
~AndroidInputSystem() override;
void SetGunTouchEnabled(bool enabled);
void SetGunStickAimEnabled(bool enabled);
void SetVirtualWheelEnabled(bool enabled);
void SetVirtualShifterMode(bool shift4, bool shiftUpDown);
void SetVirtualAnalogGunEnabled(bool enabled);
@@ -113,6 +114,8 @@ private:
void SetMouseButton(int butNum, bool down);
void PulseMouseButton(int butNum, uint32_t durationMs);
void SetMousePosFromNormalized(float x, float y);
void CenterMouse();
void UpdateGunStickAim(uint32_t nowMs);
SDL_Scancode ScancodeFromSupermodelKeyName(const char* keyName) const;
static SDL_Scancode ParseFirstKeyboardScancode(const std::string& mapping, const AndroidInputSystem& self);
@@ -167,6 +170,8 @@ private:
DualScancode m_touchFishingReel{SDL_SCANCODE_SPACE, SDL_SCANCODE_UNKNOWN};
DualScancode m_touchFishingTension{SDL_SCANCODE_T, SDL_SCANCODE_UNKNOWN};
DualScancode m_touchCrosshairs{SDL_SCANCODE_F8, SDL_SCANCODE_UNKNOWN};
DualScancode m_touchMagPedal1{SDL_SCANCODE_A, SDL_SCANCODE_UNKNOWN};
DualScancode m_touchMagPedal2{SDL_SCANCODE_S, SDL_SCANCODE_UNKNOWN};
@@ -176,8 +181,14 @@ private:
DualScancode m_touchSkiSelect2{SDL_SCANCODE_W, SDL_SCANCODE_UNKNOWN};
bool m_gunTouchEnabled = false;
bool m_gunStickAimEnabled = false;
uint32_t m_lastGunAimTickMs = 0;
SDL_FingerID m_gunFinger = 0;
bool m_gunFingerActive = false;
bool m_mousePadActive = false;
SDL_FingerID m_mousePadFinger = 0;
float m_mousePadLastX = 0.0f;
float m_mousePadLastY = 0.0f;
bool m_virtualWheelEnabled = false;
SDL_FingerID m_wheelFinger = 0;
+142
View File
@@ -60,6 +60,13 @@ bool GlesPresenter::Init()
return false;
if (!CreateGeometry())
return false;
if (!CreateCrosshairProgram() || !CreateCrosshairGeometry()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GlesPresenter: crosshair renderer disabled");
if (m_crossProgram) { glDeleteProgram(m_crossProgram); m_crossProgram = 0; }
if (m_crossVbo) { glDeleteBuffers(1, &m_crossVbo); m_crossVbo = 0; }
if (m_crossVao) { glDeleteVertexArrays(1, &m_crossVao); m_crossVao = 0; }
m_uCrossColor = -1;
}
glGenTextures(1, &m_tex);
glBindTexture(GL_TEXTURE_2D, m_tex);
@@ -78,7 +85,11 @@ void GlesPresenter::Shutdown()
if (m_vbo) { glDeleteBuffers(1, &m_vbo); m_vbo = 0; }
if (m_vao) { glDeleteVertexArrays(1, &m_vao); m_vao = 0; }
if (m_program) { glDeleteProgram(m_program); m_program = 0; }
if (m_crossVbo) { glDeleteBuffers(1, &m_crossVbo); m_crossVbo = 0; }
if (m_crossVao) { glDeleteVertexArrays(1, &m_crossVao); m_crossVao = 0; }
if (m_crossProgram) { glDeleteProgram(m_crossProgram); m_crossProgram = 0; }
m_uTex = -1;
m_uCrossColor = -1;
m_uploadRGBA.clear();
m_outputW = m_outputH = m_srcW = m_srcH = 0;
}
@@ -161,6 +172,74 @@ bool GlesPresenter::CreateGeometry()
return true;
}
bool GlesPresenter::CreateCrosshairProgram()
{
static const char* vs = R"glsl(#version 300 es
precision highp float;
in vec2 aPos;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
}
)glsl";
static const char* fs = R"glsl(#version 300 es
precision mediump float;
uniform vec4 uColor;
layout(location=0) out vec4 oColor;
void main() {
oColor = uColor;
}
)glsl";
GLuint v = Compile(GL_VERTEX_SHADER, vs);
GLuint f = Compile(GL_FRAGMENT_SHADER, fs);
if (!v || !f)
{
if (v) glDeleteShader(v);
if (f) glDeleteShader(f);
return false;
}
m_crossProgram = glCreateProgram();
glAttachShader(m_crossProgram, v);
glAttachShader(m_crossProgram, f);
glBindAttribLocation(m_crossProgram, 0, "aPos");
glLinkProgram(m_crossProgram);
glDeleteShader(v);
glDeleteShader(f);
GLint ok = 0;
glGetProgramiv(m_crossProgram, GL_LINK_STATUS, &ok);
if (!ok)
{
char log[2048];
GLsizei n = 0;
glGetProgramInfoLog(m_crossProgram, (GLsizei)sizeof(log), &n, log);
log[(n >= (GLsizei)sizeof(log)) ? ((GLsizei)sizeof(log) - 1) : n] = '\0';
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GlesPresenter: crosshair program link failed:\n%s", log);
glDeleteProgram(m_crossProgram);
m_crossProgram = 0;
return false;
}
m_uCrossColor = glGetUniformLocation(m_crossProgram, "uColor");
return true;
}
bool GlesPresenter::CreateCrosshairGeometry()
{
glGenVertexArrays(1, &m_crossVao);
glGenBuffers(1, &m_crossVbo);
glBindVertexArray(m_crossVao);
glBindBuffer(GL_ARRAY_BUFFER, m_crossVbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 24, nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return true;
}
void GlesPresenter::Resize(int outputW, int outputH)
{
m_outputW = std::max(1, outputW);
@@ -281,3 +360,66 @@ void GlesPresenter::Render(bool alphaBlend)
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
}
void GlesPresenter::RenderCrosshair(float xNorm, float yNorm, float r, float g, float b, float aspect,
int viewX, int viewY, int viewW, int viewH)
{
if (!m_crossProgram || !m_crossVao || viewW <= 0 || viewH <= 0)
return;
auto toNdc = [](float x, float y, float& outX, float& outY) {
outX = (x * 2.0f) - 1.0f;
outY = 1.0f - (y * 2.0f);
};
const float base = 0.01f;
const float height = 0.02f;
const float dist = 0.004f;
const float bx = base * 0.5f;
const float hy = (dist + height) * aspect;
const float by = bx * aspect;
float verts[24];
int idx = 0;
auto emit = [&](float x, float y) {
float nx, ny;
toNdc(x, y, nx, ny);
verts[idx++] = nx;
verts[idx++] = ny;
};
// Bottom triangle
emit(xNorm, yNorm + dist);
emit(xNorm + bx, yNorm + hy);
emit(xNorm - bx, yNorm + hy);
// Top triangle
emit(xNorm, yNorm - dist);
emit(xNorm - bx, yNorm - hy);
emit(xNorm + bx, yNorm - hy);
// Left triangle
emit(xNorm - dist, yNorm);
emit(xNorm - dist - height, yNorm + by);
emit(xNorm - dist - height, yNorm - by);
// Right triangle
emit(xNorm + dist, yNorm);
emit(xNorm + dist + height, yNorm - by);
emit(xNorm + dist + height, yNorm + by);
glViewport(viewX, viewY, viewW, viewH);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_STENCIL_TEST);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(m_crossProgram);
glUniform4f(m_uCrossColor, r, g, b, 1.0f);
glBindVertexArray(m_crossVao);
glBindBuffer(GL_ARRAY_BUFFER, m_crossVbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 12);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(0);
}
@@ -18,10 +18,13 @@ public:
// pixelsARGB: 0xAARRGGBB packed.
void UpdateFrameARGB(const uint32_t* pixelsARGB, int width, int height);
void Render(bool alphaBlend);
void RenderCrosshair(float xNorm, float yNorm, float r, float g, float b, float aspect, int viewX, int viewY, int viewW, int viewH);
private:
bool CreateProgram();
bool CreateGeometry();
bool CreateCrosshairProgram();
bool CreateCrosshairGeometry();
void UpdateQuadVerts();
int m_outputW = 0;
@@ -36,6 +39,11 @@ private:
unsigned m_tex = 0;
int m_uTex = -1;
unsigned m_crossProgram = 0;
unsigned m_crossVao = 0;
unsigned m_crossVbo = 0;
int m_uCrossColor = -1;
// Interleaved: pos.xy, uv.xy (4 verts)
float m_verts[16]{};
+106 -6
View File
@@ -197,6 +197,12 @@ static void AndroidTileBlit(const uint32_t* pixelsARGB, int width, int height, b
g_presenter->Render(alphaBlend);
}
static void GunToViewCoords(float& x, float& y)
{
x = (x - 150.0f) / (651.0f - 150.0f);
y = (y - 80.0f) / (465.0f - 80.0f);
}
struct Super3Host {
static constexpr int32_t STATE_FILE_VERSION = 3;
Util::Config::Node config{"Global"};
@@ -324,6 +330,7 @@ struct Super3Host {
config.Set("YResolution", "384");
config.Set("Throttle", true);
config.Set("RefreshRate", "60.0");
config.Set("Crosshairs", 0);
// Lightgun games: reload immediately when tapping the off-screen button.
config.Set("InputAutoTrigger", "1");
@@ -342,12 +349,13 @@ struct Super3Host {
config.Set("InputSteeringLeft", "KEY_LEFT");
config.Set("InputSteeringRight", "KEY_RIGHT");
config.Set("InputAccelerator", "KEY_W");
config.Set("InputBrake", "KEY_S");
config.Set("UISaveState", "KEY_F5");
config.Set("UIChangeSlot", "KEY_F6");
config.Set("UILoadState", "KEY_F7");
inputSystem.ApplyConfig(config);
}
config.Set("InputBrake", "KEY_S");
config.Set("UISaveState", "KEY_F5");
config.Set("UIChangeSlot", "KEY_F6");
config.Set("UILoadState", "KEY_F7");
config.Set("InputUISelectCrosshairs", "KEY_F8");
inputSystem.ApplyConfig(config);
}
void ApplyAndroidHardOverrides()
{
@@ -439,6 +447,7 @@ struct Super3Host {
ensureKeyboardFallback("InputTwinJoyShot2", "KEY_S");
ensureKeyboardFallback("InputTwinJoyTurbo1", "KEY_Z");
ensureKeyboardFallback("InputTwinJoyTurbo2", "KEY_X");
ensureKeyboardFallback("InputUISelectCrosshairs", "KEY_F8");
inputSystem.ApplyConfig(config);
}
@@ -516,6 +525,7 @@ struct Super3Host {
(game.inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0;
const bool analogJoystickGame = (game.inputs & Game::INPUT_ANALOG_JOYSTICK) != 0;
inputSystem.SetGunTouchEnabled(gunGame || analogJoystickGame);
inputSystem.SetGunStickAimEnabled(gunGame || analogJoystickGame);
const bool analogGunGame = (game.inputs & (Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0;
inputSystem.SetVirtualAnalogGunEnabled(analogGunGame);
@@ -657,6 +667,19 @@ struct Super3Host {
// it mainly keeps the input system in a sane state.
inputs.Poll(&game, 0, 0, 496, 384);
if (inputs.uiSelectCrosshairs && inputs.uiSelectCrosshairs->Pressed())
{
const bool hasGunInputs =
(game.inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0 ||
game.name == "lostwsga";
if (hasGunInputs)
{
unsigned crosshairs = config["Crosshairs"].ValueAsDefault<unsigned>(0);
crosshairs = (crosshairs + 1) & 3u;
config.Set("Crosshairs", crosshairs);
}
}
// If a physical keyboard is attached, allow the canonical hotkeys too.
if (!threadsPausedByMenu) {
if (inputs.uiSaveState && inputs.uiSaveState->Pressed()) {
@@ -790,6 +813,79 @@ static std::optional<std::string> FindFirstExisting(const std::vector<std::strin
return std::nullopt;
}
static void RenderCrosshairsIfNeeded(Super3Host& host,
GlesPresenter& presenter,
unsigned xOff,
unsigned yOff,
unsigned xRes,
unsigned yRes)
{
if (!host.model3)
return;
unsigned crosshairs = host.config["Crosshairs"].ValueAsDefault<unsigned>(0) & 3u;
if (crosshairs == 0)
return;
const uint32_t inputs = host.game.inputs;
bool hasGunInputs = (inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0;
if (host.game.name == "lostwsga")
hasGunInputs = true;
if (!hasGunInputs)
return;
float x[2] = {0.0f, 0.0f};
float y[2] = {0.0f, 0.0f};
bool offscreen[2] = {false, false};
bool have[2] = {false, false};
if (inputs & Game::INPUT_ANALOG_GUN1)
{
x[0] = host.inputs.analogGunX[0]->value / 255.0f;
y[0] = (255.0f - host.inputs.analogGunY[0]->value) / 255.0f;
offscreen[0] = host.inputs.analogTriggerLeft[0]->value || host.inputs.analogTriggerRight[0]->value;
have[0] = true;
}
else if (inputs & Game::INPUT_GUN1)
{
x[0] = (float)host.inputs.gunX[0]->value;
y[0] = (float)host.inputs.gunY[0]->value;
GunToViewCoords(x[0], y[0]);
offscreen[0] = host.inputs.trigger[0]->offscreenValue > 0;
have[0] = true;
}
if (inputs & Game::INPUT_ANALOG_GUN2)
{
x[1] = host.inputs.analogGunX[1]->value / 255.0f;
y[1] = (255.0f - host.inputs.analogGunY[1]->value) / 255.0f;
offscreen[1] = host.inputs.analogTriggerLeft[1]->value || host.inputs.analogTriggerRight[1]->value;
have[1] = true;
}
else if (inputs & Game::INPUT_GUN2)
{
x[1] = (float)host.inputs.gunX[1]->value;
y[1] = (float)host.inputs.gunY[1]->value;
GunToViewCoords(x[1], y[1]);
offscreen[1] = host.inputs.trigger[1]->offscreenValue > 0;
have[1] = true;
}
const float aspect = (yRes > 0) ? (float)xRes / (float)yRes : 1.0f;
if ((crosshairs & 1u) && have[0] && !offscreen[0])
{
presenter.RenderCrosshair(std::clamp(x[0], 0.0f, 1.0f), std::clamp(y[0], 0.0f, 1.0f),
1.0f, 0.0f, 0.0f, aspect,
(int)xOff, (int)yOff, (int)xRes, (int)yRes);
}
if ((crosshairs & 2u) && have[1] && !offscreen[1])
{
presenter.RenderCrosshair(std::clamp(x[1], 0.0f, 1.0f), std::clamp(y[1], 0.0f, 1.0f),
0.0f, 1.0f, 0.0f, aspect,
(int)xOff, (int)yOff, (int)xRes, (int)yRes);
}
}
// SDL entry point; currently a stub until the emulator core is hooked up.
extern "C" int SDL_main(int argc, char* argv[]) {
(void)argc;
@@ -1088,6 +1184,10 @@ extern "C" int SDL_main(int argc, char* argv[]) {
}
}
if (state == 1) {
RenderCrosshairsIfNeeded(host, presenter, xOff, yOff, xRes, yRes);
}
SDL_GL_SwapWindow(window);
const bool throttle = host.config["Throttle"].ValueAsDefault<bool>(true);
@@ -222,7 +222,8 @@ class Super3Activity : SDLActivity() {
)
val showFightButtons = isFighting || isSpikeout || isSoccer || isTwinJoysticks || isFishing || isSki
val showFightStick = showFightButtons || isMagTruck || needsAimStickAndButtons
val showAimPad = isGunGame || needsAimStickAndButtons
val showFightStick = (showFightButtons || isMagTruck) && !showAimPad
val showExtraButtons = showFightButtons || needsAimStickAndButtons
val shifterEnabled = getSharedPreferences("super3_prefs", MODE_PRIVATE)
@@ -241,12 +242,30 @@ class Super3Activity : SDLActivity() {
overlay.findViewById<View>(R.id.overlay_view)?.visibility =
if (isRacing && (hasViewChange || hasVr4)) View.VISIBLE else View.GONE
overlay.findViewById<MaterialButton>(R.id.overlay_reload)?.visibility =
if (isGunGame) View.VISIBLE else View.GONE
val replaceReloadWithCross = isGunGame && needsAimStickAndButtons
val reloadButton = overlay.findViewById<MaterialButton>(R.id.overlay_reload)
val crossButton = overlay.findViewById<MaterialButton>(R.id.overlay_crosshair)
reloadButton?.visibility = if (isGunGame && !replaceReloadWithCross) View.VISIBLE else View.GONE
crossButton?.visibility = if (isGunGame) View.VISIBLE else View.GONE
if (replaceReloadWithCross && crossButton != null) {
val density = resources.displayMetrics.density
val margin = (16f * density + 0.5f).toInt()
val lp = crossButton.layoutParams as? FrameLayout.LayoutParams
if (lp != null) {
lp.bottomMargin = margin
lp.marginEnd = margin
crossButton.layoutParams = lp
}
}
overlay.findViewById<View>(R.id.overlay_fight_stick)?.visibility =
if (showFightStick) View.VISIBLE else View.GONE
overlay.findViewById<View>(R.id.overlay_mouse_pad)?.visibility =
if (showAimPad) View.VISIBLE else View.GONE
overlay.findViewById<View>(R.id.overlay_fight_buttons)?.visibility =
if (showExtraButtons) View.VISIBLE else View.GONE
@@ -342,7 +361,10 @@ class Super3Activity : SDLActivity() {
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 (!replaceReloadWithCross) {
bindMomentary(R.id.overlay_reload, fingerId = 1109, x = 0.90f, y = 0.90f)
}
bindMomentary(R.id.overlay_crosshair, fingerId = 1119, x = 0.90f, y = 0.78f)
}
if (showExtraButtons) {
@@ -417,6 +439,40 @@ class Super3Activity : SDLActivity() {
overlay.findViewById<View>(R.id.overlay_fight_stick)?.setOnTouchListener(null)
}
if (showAimPad) {
val pad = overlay.findViewById<View>(R.id.overlay_mouse_pad)
pad?.setOnTouchListener { v, ev ->
val w = v.width.toFloat().coerceAtLeast(1f)
val h = v.height.toFloat().coerceAtLeast(1f)
val nx = (ev.x / w).coerceIn(0f, 1f)
val ny = (ev.y / h).coerceIn(0f, 1f)
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
v.alpha = 0.90f
nativeTouch(MotionEvent.ACTION_DOWN, 1125, nx, ny)
true
}
MotionEvent.ACTION_MOVE -> {
nativeTouch(MotionEvent.ACTION_MOVE, 1125, nx, ny)
true
}
MotionEvent.ACTION_UP -> {
v.alpha = 1.0f
nativeTouch(MotionEvent.ACTION_UP, 1125, nx, ny)
true
}
MotionEvent.ACTION_CANCEL -> {
v.alpha = 1.0f
nativeTouch(MotionEvent.ACTION_UP, 1125, nx, ny)
true
}
else -> true
}
}
} else {
overlay.findViewById<View>(R.id.overlay_mouse_pad)?.setOnTouchListener(null)
}
applyOverlayControlsEnabled(overlayControlsEnabled, persist = false)
if (isRacing || isMagTruck) {
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#22000000" />
<stroke android:width="2dp" android:color="@color/overlay_ripple" />
<corners android:radius="18dp" />
</shape>
@@ -140,6 +140,25 @@
app:rippleColor="@color/overlay_ripple"
app:strokeWidth="2dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/overlay_crosshair"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="86dp"
android:minHeight="0dp"
android:minWidth="0dp"
android:paddingHorizontal="12dp"
android:paddingVertical="8dp"
android:text="CROSS"
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="wrap_content"
@@ -239,6 +258,16 @@
</FrameLayout>
<FrameLayout
android:id="@+id/overlay_mouse_pad"
android:layout_width="220dp"
android:layout_height="180dp"
android:layout_gravity="bottom|start"
android:layout_marginStart="24dp"
android:layout_marginBottom="96dp"
android:background="@drawable/overlay_mouse_pad"
android:visibility="gone" />
<LinearLayout
android:id="@+id/overlay_fight_buttons"
android:layout_width="wrap_content"