From 3264672be0e8d80c5e5633e9dbb3d88bb908180f Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Tue, 16 Dec 2025 18:51:23 +0100 Subject: [PATCH] Fixed gameplay --- CMakeLists.txt | 2 + src/app/BackgroundManager.cpp | 165 ++++++++++++++++++++++++++++++ src/app/BackgroundManager.h | 18 ++++ src/app/Fireworks.cpp | 147 +++++++++++++++++++++++++++ src/app/Fireworks.h | 9 ++ src/main.cpp | 185 ++-------------------------------- src/ui/MenuWrappers.cpp | 9 +- 7 files changed, 355 insertions(+), 180 deletions(-) create mode 100644 src/app/BackgroundManager.cpp create mode 100644 src/app/BackgroundManager.h create mode 100644 src/app/Fireworks.cpp create mode 100644 src/app/Fireworks.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a6174a..4dcfd7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,8 @@ set(TETRIS_SOURCES src/gameplay/effects/LineEffect.cpp src/audio/SoundEffect.cpp src/ui/MenuLayout.cpp + src/app/BackgroundManager.cpp + src/app/Fireworks.cpp # State implementations (new) src/states/LoadingState.cpp src/states/MenuState.cpp diff --git a/src/app/BackgroundManager.cpp b/src/app/BackgroundManager.cpp new file mode 100644 index 0000000..c043b39 --- /dev/null +++ b/src/app/BackgroundManager.cpp @@ -0,0 +1,165 @@ +#include "app/BackgroundManager.h" +#include +#include +#include +#include +#include +#include "utils/ImagePathResolver.h" + +struct BackgroundManager::Impl { + enum class Phase { Idle, ZoomOut, ZoomIn }; + SDL_Texture* currentTex = nullptr; + SDL_Texture* nextTex = nullptr; + int currentLevel = -1; + int queuedLevel = -1; + float phaseElapsedMs = 0.0f; + float phaseDurationMs = 0.0f; + float fadeDurationMs = 1200.0f; + Phase phase = Phase::Idle; +}; + +static float getPhaseDurationMs(const BackgroundManager::Impl& fader, BackgroundManager::Impl::Phase ph) { + const float total = std::max(1200.0f, fader.fadeDurationMs); + switch (ph) { + case BackgroundManager::Impl::Phase::ZoomOut: return total * 0.45f; + case BackgroundManager::Impl::Phase::ZoomIn: return total * 0.45f; + default: return 0.0f; + } +} + +static void destroyTex(SDL_Texture*& t) { + if (t) { SDL_DestroyTexture(t); t = nullptr; } +} + +BackgroundManager::BackgroundManager() : impl(new Impl()) {} +BackgroundManager::~BackgroundManager() { reset(); delete impl; impl = nullptr; } + +bool BackgroundManager::queueLevelBackground(SDL_Renderer* renderer, int level) { + if (!renderer) return false; + level = std::clamp(level, 0, 32); + if (impl->currentLevel == level || impl->queuedLevel == level) return true; + + char bgPath[256]; + std::snprintf(bgPath, sizeof(bgPath), "assets/images/levels/level%d.jpg", level); + const std::string resolved = AssetPath::resolveImagePath(bgPath); + + SDL_Surface* s = IMG_Load(resolved.c_str()); + if (!s) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Background load failed: %s (%s)", bgPath, resolved.c_str()); + return false; + } + SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, s); + SDL_DestroySurface(s); + if (!tex) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "CreateTexture failed for %s", resolved.c_str()); + return false; + } + + destroyTex(impl->nextTex); + impl->nextTex = tex; + impl->queuedLevel = level; + + if (!impl->currentTex) { + impl->currentTex = impl->nextTex; + impl->currentLevel = impl->queuedLevel; + impl->nextTex = nullptr; + impl->queuedLevel = -1; + impl->phase = Impl::Phase::Idle; + impl->phaseElapsedMs = 0.0f; + impl->phaseDurationMs = 0.0f; + } else if (impl->phase == Impl::Phase::Idle) { + impl->phase = Impl::Phase::ZoomOut; + impl->phaseDurationMs = getPhaseDurationMs(*impl, impl->phase); + impl->phaseElapsedMs = 0.0f; + } + return true; +} + +void BackgroundManager::update(float frameMs) { + if (impl->phase == Impl::Phase::Idle) return; + if (!impl->currentTex && !impl->nextTex) { impl->phase = Impl::Phase::Idle; return; } + + impl->phaseElapsedMs += frameMs; + if (impl->phaseElapsedMs < std::max(1.0f, impl->phaseDurationMs)) return; + + if (impl->phase == Impl::Phase::ZoomOut) { + if (impl->nextTex) { + destroyTex(impl->currentTex); + impl->currentTex = impl->nextTex; + impl->currentLevel = impl->queuedLevel; + impl->nextTex = nullptr; + impl->queuedLevel = -1; + } + impl->phase = Impl::Phase::ZoomIn; + impl->phaseDurationMs = getPhaseDurationMs(*impl, impl->phase); + impl->phaseElapsedMs = 0.0f; + } else if (impl->phase == Impl::Phase::ZoomIn) { + impl->phase = Impl::Phase::Idle; + impl->phaseElapsedMs = 0.0f; + impl->phaseDurationMs = 0.0f; + } +} + +static void renderDynamic(SDL_Renderer* renderer, SDL_Texture* tex, int winW, int winH, float baseScale, float motionClockMs, float alphaMul) { + if (!renderer || !tex) return; + const float seconds = motionClockMs * 0.001f; + const float wobble = std::max(0.4f, baseScale + std::sin(seconds * 0.07f) * 0.02f + std::sin(seconds * 0.23f) * 0.01f); + const float rotation = std::sin(seconds * 0.035f) * 1.25f; + const float panX = std::sin(seconds * 0.11f) * winW * 0.02f; + const float panY = std::cos(seconds * 0.09f) * winH * 0.015f; + SDL_FRect dest{ (winW - winW * wobble) * 0.5f + panX, (winH - winH * wobble) * 0.5f + panY, winW * wobble, winH * wobble }; + SDL_FPoint center{dest.w * 0.5f, dest.h * 0.5f}; + Uint8 alpha = static_cast(std::clamp(alphaMul, 0.0f, 1.0f) * 255.0f); + SDL_SetTextureAlphaMod(tex, alpha); + SDL_RenderTextureRotated(renderer, tex, nullptr, &dest, rotation, ¢er, SDL_FLIP_NONE); + SDL_SetTextureAlphaMod(tex, 255); +} + +void BackgroundManager::render(SDL_Renderer* renderer, int winW, int winH, float motionClockMs) { + if (!renderer) return; + SDL_FRect fullRect{0.f,0.f,(float)winW,(float)winH}; + float duration = std::max(1.0f, impl->phaseDurationMs); + float progress = (impl->phase == Impl::Phase::Idle) ? 0.0f : std::clamp(impl->phaseElapsedMs / duration, 0.0f, 1.0f); + const float seconds = motionClockMs * 0.001f; + + if (impl->phase == Impl::Phase::ZoomOut) { + float scale = 1.0f + progress * 0.15f; + if (impl->currentTex) { + renderDynamic(renderer, impl->currentTex, winW, winH, scale, motionClockMs, (1.0f - progress * 0.4f)); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 0,0,0, Uint8(progress * 200.0f)); + SDL_RenderFillRect(renderer, &fullRect); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + } + } else if (impl->phase == Impl::Phase::ZoomIn) { + float scale = 1.10f - progress * 0.10f; + Uint8 alpha = Uint8((0.4f + progress * 0.6f) * 255.0f); + if (impl->currentTex) { + renderDynamic(renderer, impl->currentTex, winW, winH, scale, motionClockMs, alpha / 255.0f); + } + } else { + if (impl->currentTex) { + renderDynamic(renderer, impl->currentTex, winW, winH, 1.02f, motionClockMs, 1.0f); + float pulse = 0.35f + 0.25f * (0.5f + 0.5f * std::sin(seconds * 0.5f)); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 5,12,28, Uint8(pulse * 90.0f)); + SDL_RenderFillRect(renderer, &fullRect); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + } else if (impl->nextTex) { + renderDynamic(renderer, impl->nextTex, winW, winH, 1.02f, motionClockMs, 1.0f); + } else { + SDL_SetRenderDrawColor(renderer, 0,0,0,255); + SDL_RenderFillRect(renderer, &fullRect); + } + } +} + +void BackgroundManager::reset() { + destroyTex(impl->currentTex); + destroyTex(impl->nextTex); + impl->currentLevel = -1; + impl->queuedLevel = -1; + impl->phaseElapsedMs = 0.0f; + impl->phaseDurationMs = 0.0f; + impl->phase = Impl::Phase::Idle; +} diff --git a/src/app/BackgroundManager.h b/src/app/BackgroundManager.h new file mode 100644 index 0000000..dc53cb8 --- /dev/null +++ b/src/app/BackgroundManager.h @@ -0,0 +1,18 @@ +#pragma once +#include + +class BackgroundManager { +public: + BackgroundManager(); + ~BackgroundManager(); + + bool queueLevelBackground(SDL_Renderer* renderer, int level); + void update(float frameMs); + void render(SDL_Renderer* renderer, int winW, int winH, float motionClockMs); + void reset(); + + struct Impl; + +private: + Impl* impl; +}; diff --git a/src/app/Fireworks.cpp b/src/app/Fireworks.cpp new file mode 100644 index 0000000..8b9b01f --- /dev/null +++ b/src/app/Fireworks.cpp @@ -0,0 +1,147 @@ +#include "app/Fireworks.h" +#include +#include +#include +#include +#include + +namespace { +struct BlockParticle { + float x{}, y{}, vx{}, vy{}, size{}, alpha{}, decay{}, wobblePhase{}, wobbleSpeed{}, coreHeat{}; + BlockParticle(float sx, float sy) : x(sx), y(sy) { + const float spreadDeg = 35.0f; + const float angleDeg = -90.0f + spreadDeg * ((rand() % 200) / 100.0f - 1.0f); + const float angleRad = angleDeg * 3.1415926f / 180.0f; + float speed = 1.3f + (rand() % 220) / 80.0f; + vx = std::cos(angleRad) * speed * 0.55f; + vy = std::sin(angleRad) * speed; + size = 6.0f + (rand() % 40) / 10.0f; + alpha = 1.0f; + decay = 0.0095f + (rand() % 180) / 12000.0f; + wobblePhase = (rand() % 628) / 100.0f; + wobbleSpeed = 0.08f + (rand() % 60) / 600.0f; + coreHeat = 0.65f + (rand() % 35) / 100.0f; + } + bool update() { + vx *= 0.992f; + vy = vy * 0.985f - 0.015f; + x += vx; + y += vy; + wobblePhase += wobbleSpeed; + x += std::sin(wobblePhase) * 0.12f; + alpha -= decay; + size = std::max(1.8f, size - 0.03f); + coreHeat = std::max(0.0f, coreHeat - decay * 0.6f); + return alpha > 0.03f; + } +}; + +struct TetrisFirework { + std::vector particles; + TetrisFirework(float x, float y) { + int particleCount = 30 + rand() % 25; + particles.reserve(particleCount); + for (int i=0;iupdate()) it = particles.erase(it); + else ++it; + } + return !particles.empty(); + } +}; + +static std::vector fireworks; +static double logoAnimCounter = 0.0; +static int hoveredButton = -1; + +static SDL_Color blendFireColor(float heat, float alphaScale, Uint8 minG, Uint8 minB) { + heat = std::clamp(heat, 0.0f, 1.0f); + Uint8 r = 255; + Uint8 g = static_cast(std::clamp(120.0f + heat * (255.0f - 120.0f), float(minG), 255.0f)); + Uint8 b = static_cast(std::clamp(40.0f + (1.0f - heat) * 60.0f, float(minB), 255.0f)); + Uint8 a = static_cast(std::clamp(alphaScale * 255.0f, 0.0f, 255.0f)); + return SDL_Color{r,g,b,a}; +} +} // namespace + +namespace AppFireworks { +void update(double frameMs) { + if (fireworks.size() < 5 && (rand() % 100) < 2) { + float x = 1200.0f * 0.55f + float(rand() % int(1200.0f * 0.35f)); + float y = 1000.0f * 0.80f + float(rand() % int(1000.0f * 0.15f)); + fireworks.emplace_back(x,y); + } + for (auto it = fireworks.begin(); it != fireworks.end();) { + if (!it->update()) it = fireworks.erase(it); + else ++it; + } +} + +void draw(SDL_Renderer* renderer, SDL_Texture*) { + if (!renderer) return; + SDL_BlendMode previousBlend = SDL_BLENDMODE_NONE; + SDL_GetRenderDrawBlendMode(renderer, &previousBlend); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); + static constexpr int quadIdx[6] = {0,1,2,2,1,3}; + + auto makeV = [](float px, float py, SDL_Color c){ + SDL_Vertex v{}; + v.position.x = px; + v.position.y = py; + v.color = SDL_FColor{ c.r/255.0f, c.g/255.0f, c.b/255.0f, c.a/255.0f }; + return v; + }; + + for (auto& f : fireworks) { + for (auto& p : f.particles) { + const float heat = std::clamp(p.alpha * 1.25f + p.coreHeat * 0.5f, 0.0f, 1.0f); + SDL_Color glow = blendFireColor(0.45f + heat * 0.55f, p.alpha * 0.55f, 100, 40); + SDL_Color tailBase = blendFireColor(heat * 0.75f, p.alpha * 0.5f, 70, 25); + SDL_Color tailTip = blendFireColor(heat * 0.35f, p.alpha * 0.2f, 40, 15); + SDL_Color core = blendFireColor(heat, std::min(1.0f, p.alpha * 1.1f), 150, 80); + + float velLen = std::sqrt(p.vx*p.vx + p.vy*p.vy); + SDL_FPoint dir = velLen > 0.001f ? SDL_FPoint{p.vx/velLen,p.vy/velLen} : SDL_FPoint{0.0f,-1.0f}; + SDL_FPoint perp{-dir.y, dir.x}; + const float baseW = std::max(0.8f, p.size * 0.55f); + const float tipW = baseW * 0.35f; + const float tailLen = p.size * (3.0f + (1.0f - p.alpha) * 1.8f); + + SDL_FPoint base{p.x,p.y}; + SDL_FPoint tip{p.x + dir.x*tailLen, p.y + dir.y*tailLen}; + + SDL_Vertex tail[4]; + tail[0] = makeV(base.x + perp.x * baseW, base.y + perp.y * baseW, tailBase); + tail[1] = makeV(base.x - perp.x * baseW, base.y - perp.y * baseW, tailBase); + tail[2] = makeV(tip.x + perp.x * tipW, tip.y + perp.y * tipW, tailTip); + tail[3] = makeV(tip.x - perp.x * tipW, tip.y - perp.y * tipW, tailTip); + SDL_RenderGeometry(renderer, nullptr, tail, 4, quadIdx, 6); + + const float glowAlong = p.size * 0.95f; + const float glowAcross = p.size * 0.6f; + SDL_Vertex glowV[4]; + glowV[0] = makeV(base.x + dir.x * glowAlong, base.y + dir.y * glowAlong, glow); + glowV[1] = makeV(base.x - dir.x * glowAlong, base.y - dir.y * glowAlong, glow); + glowV[2] = makeV(base.x + perp.x * glowAcross, base.y + perp.y * glowAcross, glow); + glowV[3] = makeV(base.x - perp.x * glowAcross, base.y - perp.y * glowAcross, glow); + SDL_RenderGeometry(renderer, nullptr, glowV, 4, quadIdx, 6); + + const float coreW = p.size * 0.35f; + const float coreH = p.size * 0.9f; + SDL_Vertex coreV[4]; + coreV[0] = makeV(base.x + perp.x * coreW, base.y + perp.y * coreW, core); + coreV[1] = makeV(base.x - perp.x * coreW, base.y - perp.y * coreW, core); + coreV[2] = makeV(base.x + dir.x * coreH, base.y + dir.y * coreH, core); + coreV[3] = makeV(base.x - dir.x * coreH, base.y - dir.y * coreH, core); + SDL_RenderGeometry(renderer, nullptr, coreV, 4, quadIdx, 6); + } + } + + SDL_SetRenderDrawBlendMode(renderer, previousBlend); +} + +double getLogoAnimCounter() { return logoAnimCounter; } +int getHoveredButton() { return hoveredButton; } +} // namespace AppFireworks diff --git a/src/app/Fireworks.h b/src/app/Fireworks.h new file mode 100644 index 0000000..fc4ca62 --- /dev/null +++ b/src/app/Fireworks.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace AppFireworks { + void draw(SDL_Renderer* renderer, SDL_Texture* tex); + void update(double frameMs); + double getLogoAnimCounter(); + int getHoveredButton(); +} diff --git a/src/main.cpp b/src/main.cpp index c731acc..2e91f71 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -398,6 +398,8 @@ static void resetLevelBackgrounds(LevelBackgroundFader& fader) { // ----------------------------------------------------------------------------- // Intro/Menu state variables // ----------------------------------------------------------------------------- +#include "app/BackgroundManager.h" +#include "app/Fireworks.h" static double logoAnimCounter = 0.0; static bool showSettingsPopup = false; static bool showHelpOverlay = false; @@ -409,176 +411,7 @@ static bool isNewHighScore = false; static std::string playerName = ""; static bool helpOverlayPausedGame = false; -// ----------------------------------------------------------------------------- -// Tetris Block Fireworks for intro animation (block particles) -// Forward declare block render helper used by particles -// Forward declare block render helper used by particles -// (Note: drawBlockTexture implementation was removed, so this is likely dead code unless particles use it. -// However, particles use drawFireworks_impl which uses SDL_RenderGeometry, so this is unused.) -// ----------------------------------------------------------------------------- -struct BlockParticle { - float x{}, y{}; - float vx{}, vy{}; - float size{}, alpha{}, decay{}; - float wobblePhase{}, wobbleSpeed{}; - float coreHeat{}; - BlockParticle(float sx, float sy) - : x(sx), y(sy) { - const float spreadDeg = 35.0f; - const float angleDeg = -90.0f + spreadDeg * ((rand() % 200) / 100.0f - 1.0f); // bias upward - const float angleRad = angleDeg * 3.1415926f / 180.0f; - float speed = 1.3f + (rand() % 220) / 80.0f; // ~1.3..4.05 - vx = std::cos(angleRad) * speed * 0.55f; - vy = std::sin(angleRad) * speed; - size = 6.0f + (rand() % 40) / 10.0f; // 6..10 px - alpha = 1.0f; - decay = 0.0095f + (rand() % 180) / 12000.0f; // 0.0095..0.0245 - wobblePhase = (rand() % 628) / 100.0f; - wobbleSpeed = 0.08f + (rand() % 60) / 600.0f; - coreHeat = 0.65f + (rand() % 35) / 100.0f; - } - bool update() { - vx *= 0.992f; - vy = vy * 0.985f - 0.015f; // buoyancy pushes upward (negative vy) - x += vx; - y += vy; - wobblePhase += wobbleSpeed; - x += std::sin(wobblePhase) * 0.12f; - alpha -= decay; - size = std::max(1.8f, size - 0.03f); - coreHeat = std::max(0.0f, coreHeat - decay * 0.6f); - return alpha > 0.03f; - } -}; - -struct TetrisFirework { - std::vector particles; - int mode = 0; // 0=random,1=red,2=green,3=palette - TetrisFirework(float x, float y) { - mode = rand() % 4; - int particleCount = 30 + rand() % 25; // 30-55 particles - particles.reserve(particleCount); - for (int i = 0; i < particleCount; ++i) particles.emplace_back(x, y); - } - bool update() { - for (auto it = particles.begin(); it != particles.end();) { - if (!it->update()) it = particles.erase(it); else ++it; - } - return !particles.empty(); - } - // Drawing is handled by drawFireworks_impl which accepts the texture to use. -}; - -static std::vector fireworks; -static Uint64 lastFireworkTime = 0; - -// ----------------------------------------------------------------------------- -// Fireworks Management -// ----------------------------------------------------------------------------- -static void updateFireworks(double frameMs) { - Uint64 now = SDL_GetTicks(); - // Randomly spawn new block fireworks (2% chance per frame), bias to lower-right - if (fireworks.size() < 5 && (rand() % 100) < 2) { - float x = LOGICAL_W * 0.55f + float(rand() % int(LOGICAL_W * 0.35f)); - float y = LOGICAL_H * 0.80f + float(rand() % int(LOGICAL_H * 0.15f)); - fireworks.emplace_back(x, y); - lastFireworkTime = now; - } - - // Update existing fireworks - for (auto it = fireworks.begin(); it != fireworks.end();) { - if (!it->update()) { - it = fireworks.erase(it); - } else { - ++it; - } - } -} - -// Primary implementation that accepts a texture pointer -static SDL_Color blendFireColor(float heat, float alphaScale, Uint8 minG, Uint8 minB) { - heat = std::clamp(heat, 0.0f, 1.0f); - Uint8 r = 255; - Uint8 g = static_cast(std::clamp(120.0f + heat * (255.0f - 120.0f), float(minG), 255.0f)); - Uint8 b = static_cast(std::clamp(40.0f + (1.0f - heat) * 60.0f, float(minB), 255.0f)); - Uint8 a = static_cast(std::clamp(alphaScale * 255.0f, 0.0f, 255.0f)); - return SDL_Color{r, g, b, a}; -} - -static void drawFireworks_impl(SDL_Renderer* renderer, SDL_Texture*) { - SDL_BlendMode previousBlend = SDL_BLENDMODE_NONE; - SDL_GetRenderDrawBlendMode(renderer, &previousBlend); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); - - static constexpr int quadIndices[6] = {0, 1, 2, 2, 1, 3}; - - auto makeVertex = [](float px, float py, SDL_Color c) { - SDL_Vertex v{}; - v.position.x = px; - v.position.y = py; - v.color = SDL_FColor{ - c.r / 255.0f, - c.g / 255.0f, - c.b / 255.0f, - c.a / 255.0f - }; - return v; - }; - - for (auto& f : fireworks) { - for (auto &p : f.particles) { - const float heat = std::clamp(p.alpha * 1.25f + p.coreHeat * 0.5f, 0.0f, 1.0f); - SDL_Color glowColor = blendFireColor(0.45f + heat * 0.55f, p.alpha * 0.55f, 100, 40); - SDL_Color tailBaseColor = blendFireColor(heat * 0.75f, p.alpha * 0.5f, 70, 25); - SDL_Color tailTipColor = blendFireColor(heat * 0.35f, p.alpha * 0.2f, 40, 15); - SDL_Color coreColor = blendFireColor(heat, std::min(1.0f, p.alpha * 1.1f), 150, 80); - - float velLen = std::sqrt(p.vx * p.vx + p.vy * p.vy); - SDL_FPoint dir = velLen > 0.001f ? SDL_FPoint{p.vx / velLen, p.vy / velLen} - : SDL_FPoint{0.0f, -1.0f}; - SDL_FPoint perp{-dir.y, dir.x}; - - const float baseWidth = std::max(0.8f, p.size * 0.55f); - const float tipWidth = baseWidth * 0.35f; - const float tailLen = p.size * (3.0f + (1.0f - p.alpha) * 1.8f); - - SDL_FPoint base{p.x, p.y}; - SDL_FPoint tip{p.x + dir.x * tailLen, p.y + dir.y * tailLen}; - - SDL_Vertex tailVerts[4]; - tailVerts[0] = makeVertex(base.x + perp.x * baseWidth, base.y + perp.y * baseWidth, tailBaseColor); - tailVerts[1] = makeVertex(base.x - perp.x * baseWidth, base.y - perp.y * baseWidth, tailBaseColor); - tailVerts[2] = makeVertex(tip.x + perp.x * tipWidth, tip.y + perp.y * tipWidth, tailTipColor); - tailVerts[3] = makeVertex(tip.x - perp.x * tipWidth, tip.y - perp.y * tipWidth, tailTipColor); - SDL_RenderGeometry(renderer, nullptr, tailVerts, 4, quadIndices, 6); - - const float glowAlong = p.size * 0.95f; - const float glowAcross = p.size * 0.6f; - SDL_Vertex glowVerts[4]; - glowVerts[0] = makeVertex(base.x + dir.x * glowAlong, base.y + dir.y * glowAlong, glowColor); - glowVerts[1] = makeVertex(base.x - dir.x * glowAlong, base.y - dir.y * glowAlong, glowColor); - glowVerts[2] = makeVertex(base.x + perp.x * glowAcross, base.y + perp.y * glowAcross, glowColor); - glowVerts[3] = makeVertex(base.x - perp.x * glowAcross, base.y - perp.y * glowAcross, glowColor); - SDL_RenderGeometry(renderer, nullptr, glowVerts, 4, quadIndices, 6); - - const float coreWidth = p.size * 0.35f; - const float coreHeight = p.size * 0.9f; - SDL_Vertex coreVerts[4]; - coreVerts[0] = makeVertex(base.x + perp.x * coreWidth, base.y + perp.y * coreWidth, coreColor); - coreVerts[1] = makeVertex(base.x - perp.x * coreWidth, base.y - perp.y * coreWidth, coreColor); - coreVerts[2] = makeVertex(base.x + dir.x * coreHeight, base.y + dir.y * coreHeight, coreColor); - coreVerts[3] = makeVertex(base.x - dir.x * coreHeight, base.y - dir.y * coreHeight, coreColor); - SDL_RenderGeometry(renderer, nullptr, coreVerts, 4, quadIndices, 6); - } - } - - SDL_SetRenderDrawBlendMode(renderer, previousBlend); -} -// External wrappers retained for compatibility; now no-ops to disable the legacy fireworks effect. -void menu_drawFireworks(SDL_Renderer*, SDL_Texture*) {} -void menu_updateFireworks(double) {} -double menu_getLogoAnimCounter() { return logoAnimCounter; } -int menu_getHoveredButton() { return hoveredButton; } +// Fireworks implementation moved to app/Fireworks.{h,cpp} int main(int, char **) { @@ -688,8 +521,8 @@ int main(int, char **) int mainScreenW = 0, mainScreenH = 0; SDL_Texture* mainScreenTex = nullptr; - // Level background caching system - LevelBackgroundFader levelBackgrounds; + // Level background manager (moved to BackgroundManager) + BackgroundManager levelBackgrounds; // Default start level selection: 0 (declare here so it's in scope for all handlers) int startLevelSelection = 0; @@ -1574,7 +1407,7 @@ int main(int, char **) } // Advance level background fade if a next texture is queued - updateLevelBackgroundFade(levelBackgrounds, float(frameMs)); + levelBackgrounds.update(float(frameMs)); // Update intro animations if (state == AppState::Menu) { @@ -1690,8 +1523,8 @@ int main(int, char **) // Draw level-based background for gameplay, starfield for other states if (state == AppState::Playing) { int bgLevel = std::clamp(game.level(), 0, 32); - queueLevelBackground(levelBackgrounds, renderer, bgLevel); - renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH, static_cast(gameplayBackgroundClockMs)); + levelBackgrounds.queueLevelBackground(renderer, bgLevel); + levelBackgrounds.render(renderer, winW, winH, static_cast(gameplayBackgroundClockMs)); } else if (state == AppState::Loading) { // Use 3D starfield for loading screen (full screen) starfield3D.draw(renderer); @@ -2135,7 +1968,7 @@ int main(int, char **) SDL_DestroyTexture(logoTex); if (mainScreenTex) SDL_DestroyTexture(mainScreenTex); - resetLevelBackgrounds(levelBackgrounds); + levelBackgrounds.reset(); if (blocksTex) SDL_DestroyTexture(blocksTex); if (scorePanelTex) diff --git a/src/ui/MenuWrappers.cpp b/src/ui/MenuWrappers.cpp index cb23a98..ceeb95c 100644 --- a/src/ui/MenuWrappers.cpp +++ b/src/ui/MenuWrappers.cpp @@ -2,6 +2,7 @@ #include "MenuWrappers.h" #include "../core/GlobalState.h" #include "../graphics/Font.h" +#include "app/Fireworks.h" #include using namespace Globals; @@ -13,19 +14,19 @@ static void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, } void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) { - GlobalState::instance().drawFireworks(renderer, blocksTex); + AppFireworks::draw(renderer, blocksTex); } void menu_updateFireworks(double frameMs) { - GlobalState::instance().updateFireworks(frameMs); + AppFireworks::update(frameMs); } double menu_getLogoAnimCounter() { - return GlobalState::instance().logoAnimCounter; + return AppFireworks::getLogoAnimCounter(); } int menu_getHoveredButton() { - return GlobalState::instance().hoveredButton; + return AppFireworks::getHoveredButton(); } void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,