mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
added mouse pad and crosshairs button for gun games
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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]{};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user