diff --git a/Src/Graphics/New3D/New3D.cpp b/Src/Graphics/New3D/New3D.cpp index f6d8801..f6827de 100644 --- a/Src/Graphics/New3D/New3D.cpp +++ b/Src/Graphics/New3D/New3D.cpp @@ -417,9 +417,10 @@ void CNew3D::RenderFrame(void) } } - // Draw to default framebuffer. - glDisable(GL_STENCIL_TEST); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Draw to default framebuffer. + glDisable(GL_STENCIL_TEST); + // Android compositor draws TileGen bottom surface first; keep color, clear depth for 3D. + glClear(GL_DEPTH_BUFFER_BIT); for (int pri = 0; pri <= 3; pri++) { if (SkipLayer(pri)) continue; diff --git a/android/app/src/main/assets/Config/Supermodel.ini b/android/app/src/main/assets/Config/Supermodel.ini index 7dc7af6..2d8e673 100644 --- a/android/app/src/main/assets/Config/Supermodel.ini +++ b/android/app/src/main/assets/Config/Supermodel.ini @@ -132,7 +132,7 @@ InputGunX = MOUSE_XAXIS,JOY1_XAXIS InputGunY = MOUSE_YAXIS,JOY1_YAXIS InputTrigger = KEY_A,JOY1_BUTTON1,MOUSE_LEFT_BUTTON InputOffscreen = KEY_S,JOY1_BUTTON2,MOUSE_RIGHT_BUTTON -InputAutoTrigger = 0 +InputAutoTrigger = 1 InputGunLeft2 = NONE InputGunRight2 = NONE InputGunUp2 = NONE @@ -141,7 +141,7 @@ InputGunX2 = JOY2_XAXIS InputGunY2 = JOY2_YAXIS InputTrigger2 = JOY2_BUTTON1 InputOffscreen2 = JOY2_BUTTON2 -InputAutoTrigger2 = 0 +InputAutoTrigger2 = 1 InputAnalogGunLeft = KEY_LEFT InputAnalogGunRight = KEY_RIGHT InputAnalogGunUp = KEY_UP diff --git a/android/app/src/main/cpp/android_input_system.cpp b/android/app/src/main/cpp/android_input_system.cpp index e3cebb1..aaa49d0 100644 --- a/android/app/src/main/cpp/android_input_system.cpp +++ b/android/app/src/main/cpp/android_input_system.cpp @@ -143,6 +143,8 @@ void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config) return config[key].ValueAsDefault(fallback); }; + m_gunAutoTrigger = config["InputAutoTrigger"].ValueAsDefault(0) != 0; + auto keySc = [&](const std::string& mapping, SDL_Scancode fallback) -> SDL_Scancode { if (mapping.empty()) return fallback; SDL_Scancode sc = ParseFirstKeyboardScancode(mapping, *this); @@ -228,6 +230,8 @@ bool AndroidInputSystem::InitializeSystem() std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs)); m_gunFingerActive = false; m_gunFinger = 0; + m_suppressGunTriggerUntilMs = 0; + m_pendingReloadTriggerAtMs = 0; m_wheelFingerActive = false; m_wheelFinger = 0; m_virtualJoyX.store(0, std::memory_order_relaxed); @@ -271,6 +275,14 @@ bool AndroidInputSystem::Poll() SDL_GameControllerUpdate(); const uint32_t now = SDL_GetTicks(); + if (m_pendingReloadTriggerAtMs != 0 && now >= m_pendingReloadTriggerAtMs) + { + // Manual reload helper when the core's InputAutoTrigger is disabled: + // fire the trigger shortly after offscreen is pressed (offscreen->trigger order). + m_suppressGunTriggerUntilMs = 0; + PulseMouseButton(0, 80); + m_pendingReloadTriggerAtMs = 0; + } for (auto it = m_pulseUntilMs.begin(); it != m_pulseUntilMs.end();) { if (now >= it->second) @@ -578,11 +590,16 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) { if (down) { - // Most lightgun games treat "reload" as offscreen + trigger. - // If the player is already holding the trigger (aim finger active), only pulse offscreen. + // Temporarily suppress trigger so reload happens in the correct order even if the + // user is holding the screen to aim/fire (our touch mapping holds MOUSE_LEFT_BUTTON). + m_suppressGunTriggerUntilMs = SDL_GetTicks() + 150; + + // Keep offscreen asserted long enough that the core's poll loop won't miss it. PulseMouseButton(2, 140); // right = reload/offscreen - if (!m_gunFingerActive) - PulseMouseButton(0, 80); // left = trigger pulse (only when not already held) + + // If the core auto-trigger is disabled, approximate the same behavior ourselves. + if (!m_gunAutoTrigger) + m_pendingReloadTriggerAtMs = SDL_GetTicks() + 90; } return; } @@ -814,6 +831,13 @@ bool AndroidInputSystem::IsMouseButPressed(int mseNum, int butNum) return false; if (butNum < 0 || butNum >= kMouseButtons) return false; + if (butNum == 0 && m_suppressGunTriggerUntilMs != 0) + { + const uint32_t now = SDL_GetTicks(); + if (now < m_suppressGunTriggerUntilMs) + return false; + m_suppressGunTriggerUntilMs = 0; + } return m_mouseButtons[butNum] != 0; } diff --git a/android/app/src/main/cpp/android_input_system.h b/android/app/src/main/cpp/android_input_system.h index 429a95e..4017cfe 100644 --- a/android/app/src/main/cpp/android_input_system.h +++ b/android/app/src/main/cpp/android_input_system.h @@ -192,6 +192,14 @@ private: uint8_t m_mouseButtons[kMouseButtons]{}; uint32_t m_mouseButtonPulseUntilMs[kMouseButtons]{}; + // Lightgun reload behavior: + // - If InputAutoTrigger is enabled, we only need to assert Offscreen; the core will + // synthesize trigger timing. We also temporarily suppress the trigger so reload + // happens in the correct order even if the user is holding the screen to aim/fire. + bool m_gunAutoTrigger = false; + uint32_t m_suppressGunTriggerUntilMs = 0; + uint32_t m_pendingReloadTriggerAtMs = 0; + std::vector m_controllers; std::vector m_keys; // indexed by SDL_Scancode std::unordered_map m_fingerHeldDir; diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index 8e2fa0e..8706f2e 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -37,6 +37,92 @@ static std::string JoinPath(const std::string& a, const std::string& b); +class CompositedRender3D final : public IRender3D +{ +public: + CompositedRender3D(IRender3D* inner, CRender2D* render2d, GlesPresenter* presenter) + : m_inner(inner), m_render2d(render2d), m_presenter(presenter) + { + } + + void RenderFrame(void) override + { + if (m_presenter && m_render2d && m_render2d->HasFrame()) + { + const uint32_t* pixels = m_render2d->GetFrameBufferRGBA(); + m_presenter->UpdateFrameARGB(pixels, (int)m_render2d->GetFrameWidth(), (int)m_render2d->GetFrameHeight()); + m_presenter->Render(false); + } + if (m_inner) + m_inner->RenderFrame(); + } + + void BeginFrame(void) override + { + if (m_inner) + m_inner->BeginFrame(); + } + + void EndFrame(void) override + { + if (m_inner) + m_inner->EndFrame(); + } + + void UploadTextures(unsigned level, unsigned x, unsigned y, unsigned width, unsigned height) override + { + if (m_inner) + m_inner->UploadTextures(level, x, y, width, height); + } + + void AttachMemory(const uint32_t* cullingRAMLoPtr, + const uint32_t* cullingRAMHiPtr, + const uint32_t* polyRAMPtr, + const uint32_t* vromPtr, + const uint16_t* textureRAMPtr) override + { + if (m_inner) + m_inner->AttachMemory(cullingRAMLoPtr, cullingRAMHiPtr, polyRAMPtr, vromPtr, textureRAMPtr); + } + + void SetStepping(int stepping) override + { + if (m_inner) + m_inner->SetStepping(stepping); + } + + bool Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yRes, unsigned totalXRes, unsigned totalYRes) override + { + if (!m_inner) + return true; + return m_inner->Init(xOffset, yOffset, xRes, yRes, totalXRes, totalYRes); + } + + void SetSunClamp(bool enable) override + { + if (m_inner) + m_inner->SetSunClamp(enable); + } + + void SetSignedShade(bool enable) override + { + if (m_inner) + m_inner->SetSignedShade(enable); + } + + float GetLosValue(int layer) override + { + if (!m_inner) + return 0.0f; + return m_inner->GetLosValue(layer); + } + +private: + IRender3D* m_inner = nullptr; + CRender2D* m_render2d = nullptr; + GlesPresenter* m_presenter = nullptr; +}; + class NullRender3D : public IRender3D { public: void RenderFrame() override {} @@ -71,6 +157,7 @@ struct Super3Host { StubOutputs outputs; GlesStubRender3D stub3d; std::unique_ptr new3d; + std::unique_ptr composited3d; IRender3D* render3d = &stub3d; CRender2D render2d{config}; @@ -188,6 +275,10 @@ struct Super3Host { config.Set("XResolution", "496"); config.Set("YResolution", "384"); + // Lightgun games: reload immediately when tapping the off-screen button. + config.Set("InputAutoTrigger", "1"); + config.Set("InputAutoTrigger2", "1"); + // Minimal default input bindings (keyboard scancodes). On Android, our input // system synthesizes these via touch/controller. config.Set("InputCoin1", "KEY_5"); @@ -598,7 +689,13 @@ struct Super3Host { SDL_Log("Loaded state from '%s'.", filePath.c_str()); } - bool InstallNew3D(unsigned xOff, unsigned yOff, unsigned xRes, unsigned yRes, unsigned totalXRes, unsigned totalYRes) + bool InstallNew3D(GlesPresenter* presenter, + unsigned xOff, + unsigned yOff, + unsigned xRes, + unsigned yRes, + unsigned totalXRes, + unsigned totalYRes) { if (!model3 || new3d) return !!new3d; @@ -613,7 +710,8 @@ struct Super3Host { return false; } - render3d = new3d.get(); + composited3d = std::make_unique(new3d.get(), &render2d, presenter); + render3d = composited3d.get(); model3->AttachRenderers(&render2d, render3d); SDL_Log("New3D attached"); return true; @@ -899,16 +997,16 @@ extern "C" int SDL_main(int argc, char* argv[]) { } if (!new3dAttached) { - new3dAttached = host.InstallNew3D(xOff, yOff, xRes, yRes, totalXRes, totalYRes); + new3dAttached = host.InstallNew3D(&presenter, xOff, yOff, xRes, yRes, totalXRes, totalYRes); } if (new3dAttached) { // 3D path: let New3D draw into the default framebuffer from inside the core (scissored). + presenter.Resize(winW, winH); + presenter.SetStretch(false); host.RunFrame(); // Overlay TileGen top layers (HUD/menus) on top of 3D. - presenter.Resize(winW, winH); - presenter.SetStretch(false); if (host.render2d.HasTopSurface()) { const uint32_t* pixels = host.render2d.GetTopSurfaceARGB(); presenter.UpdateFrameARGB(pixels, (int)host.render2d.GetFrameWidth(), (int)host.render2d.GetFrameHeight()); @@ -916,9 +1014,9 @@ extern "C" int SDL_main(int argc, char* argv[]) { } } else { // 2D-only path: keep showing TileGen software output. - host.RunFrame(); presenter.Resize(winW, winH); presenter.SetStretch(wideBackground); + host.RunFrame(); if (host.render2d.HasFrame()) { const uint32_t* pixels = host.render2d.GetFrameBufferRGBA(); presenter.UpdateFrameARGB(pixels, (int)host.render2d.GetFrameWidth(), (int)host.render2d.GetFrameHeight()); diff --git a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt index 512c7a0..ac73646 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/AssetInstaller.kt @@ -55,6 +55,8 @@ object AssetInstaller { "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", + "InputAutoTrigger = 0" to "InputAutoTrigger = 1", + "InputAutoTrigger2 = 0" to "InputAutoTrigger2 = 1", ) val updated = lines.map { line ->