Fixed gameplay
This commit is contained in:
@ -53,6 +53,8 @@ set(TETRIS_SOURCES
|
|||||||
src/gameplay/effects/LineEffect.cpp
|
src/gameplay/effects/LineEffect.cpp
|
||||||
src/audio/SoundEffect.cpp
|
src/audio/SoundEffect.cpp
|
||||||
src/ui/MenuLayout.cpp
|
src/ui/MenuLayout.cpp
|
||||||
|
src/app/BackgroundManager.cpp
|
||||||
|
src/app/Fireworks.cpp
|
||||||
# State implementations (new)
|
# State implementations (new)
|
||||||
src/states/LoadingState.cpp
|
src/states/LoadingState.cpp
|
||||||
src/states/MenuState.cpp
|
src/states/MenuState.cpp
|
||||||
|
|||||||
165
src/app/BackgroundManager.cpp
Normal file
165
src/app/BackgroundManager.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include "app/BackgroundManager.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3_image/SDL_image.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#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<Uint8>(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;
|
||||||
|
}
|
||||||
18
src/app/BackgroundManager.h
Normal file
18
src/app/BackgroundManager.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
147
src/app/Fireworks.cpp
Normal file
147
src/app/Fireworks.cpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include "app/Fireworks.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
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<BlockParticle> particles;
|
||||||
|
TetrisFirework(float x, float y) {
|
||||||
|
int particleCount = 30 + rand() % 25;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<TetrisFirework> 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<Uint8>(std::clamp(120.0f + heat * (255.0f - 120.0f), float(minG), 255.0f));
|
||||||
|
Uint8 b = static_cast<Uint8>(std::clamp(40.0f + (1.0f - heat) * 60.0f, float(minB), 255.0f));
|
||||||
|
Uint8 a = static_cast<Uint8>(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
|
||||||
9
src/app/Fireworks.h
Normal file
9
src/app/Fireworks.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
namespace AppFireworks {
|
||||||
|
void draw(SDL_Renderer* renderer, SDL_Texture* tex);
|
||||||
|
void update(double frameMs);
|
||||||
|
double getLogoAnimCounter();
|
||||||
|
int getHoveredButton();
|
||||||
|
}
|
||||||
185
src/main.cpp
185
src/main.cpp
@ -398,6 +398,8 @@ static void resetLevelBackgrounds(LevelBackgroundFader& fader) {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Intro/Menu state variables
|
// Intro/Menu state variables
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
#include "app/BackgroundManager.h"
|
||||||
|
#include "app/Fireworks.h"
|
||||||
static double logoAnimCounter = 0.0;
|
static double logoAnimCounter = 0.0;
|
||||||
static bool showSettingsPopup = false;
|
static bool showSettingsPopup = false;
|
||||||
static bool showHelpOverlay = false;
|
static bool showHelpOverlay = false;
|
||||||
@ -409,176 +411,7 @@ static bool isNewHighScore = false;
|
|||||||
static std::string playerName = "";
|
static std::string playerName = "";
|
||||||
static bool helpOverlayPausedGame = false;
|
static bool helpOverlayPausedGame = false;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// Fireworks implementation moved to app/Fireworks.{h,cpp}
|
||||||
// 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<BlockParticle> 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<TetrisFirework> 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<Uint8>(std::clamp(120.0f + heat * (255.0f - 120.0f), float(minG), 255.0f));
|
|
||||||
Uint8 b = static_cast<Uint8>(std::clamp(40.0f + (1.0f - heat) * 60.0f, float(minB), 255.0f));
|
|
||||||
Uint8 a = static_cast<Uint8>(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; }
|
|
||||||
|
|
||||||
int main(int, char **)
|
int main(int, char **)
|
||||||
{
|
{
|
||||||
@ -688,8 +521,8 @@ int main(int, char **)
|
|||||||
int mainScreenW = 0, mainScreenH = 0;
|
int mainScreenW = 0, mainScreenH = 0;
|
||||||
SDL_Texture* mainScreenTex = nullptr;
|
SDL_Texture* mainScreenTex = nullptr;
|
||||||
|
|
||||||
// Level background caching system
|
// Level background manager (moved to BackgroundManager)
|
||||||
LevelBackgroundFader levelBackgrounds;
|
BackgroundManager levelBackgrounds;
|
||||||
|
|
||||||
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
||||||
int startLevelSelection = 0;
|
int startLevelSelection = 0;
|
||||||
@ -1574,7 +1407,7 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Advance level background fade if a next texture is queued
|
// Advance level background fade if a next texture is queued
|
||||||
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
|
levelBackgrounds.update(float(frameMs));
|
||||||
|
|
||||||
// Update intro animations
|
// Update intro animations
|
||||||
if (state == AppState::Menu) {
|
if (state == AppState::Menu) {
|
||||||
@ -1690,8 +1523,8 @@ int main(int, char **)
|
|||||||
// Draw level-based background for gameplay, starfield for other states
|
// Draw level-based background for gameplay, starfield for other states
|
||||||
if (state == AppState::Playing) {
|
if (state == AppState::Playing) {
|
||||||
int bgLevel = std::clamp(game.level(), 0, 32);
|
int bgLevel = std::clamp(game.level(), 0, 32);
|
||||||
queueLevelBackground(levelBackgrounds, renderer, bgLevel);
|
levelBackgrounds.queueLevelBackground(renderer, bgLevel);
|
||||||
renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH, static_cast<float>(gameplayBackgroundClockMs));
|
levelBackgrounds.render(renderer, winW, winH, static_cast<float>(gameplayBackgroundClockMs));
|
||||||
} else if (state == AppState::Loading) {
|
} else if (state == AppState::Loading) {
|
||||||
// Use 3D starfield for loading screen (full screen)
|
// Use 3D starfield for loading screen (full screen)
|
||||||
starfield3D.draw(renderer);
|
starfield3D.draw(renderer);
|
||||||
@ -2135,7 +1968,7 @@ int main(int, char **)
|
|||||||
SDL_DestroyTexture(logoTex);
|
SDL_DestroyTexture(logoTex);
|
||||||
if (mainScreenTex)
|
if (mainScreenTex)
|
||||||
SDL_DestroyTexture(mainScreenTex);
|
SDL_DestroyTexture(mainScreenTex);
|
||||||
resetLevelBackgrounds(levelBackgrounds);
|
levelBackgrounds.reset();
|
||||||
if (blocksTex)
|
if (blocksTex)
|
||||||
SDL_DestroyTexture(blocksTex);
|
SDL_DestroyTexture(blocksTex);
|
||||||
if (scorePanelTex)
|
if (scorePanelTex)
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
#include "MenuWrappers.h"
|
#include "MenuWrappers.h"
|
||||||
#include "../core/GlobalState.h"
|
#include "../core/GlobalState.h"
|
||||||
#include "../graphics/Font.h"
|
#include "../graphics/Font.h"
|
||||||
|
#include "app/Fireworks.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
using namespace Globals;
|
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) {
|
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) {
|
||||||
GlobalState::instance().drawFireworks(renderer, blocksTex);
|
AppFireworks::draw(renderer, blocksTex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void menu_updateFireworks(double frameMs) {
|
void menu_updateFireworks(double frameMs) {
|
||||||
GlobalState::instance().updateFireworks(frameMs);
|
AppFireworks::update(frameMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
double menu_getLogoAnimCounter() {
|
double menu_getLogoAnimCounter() {
|
||||||
return GlobalState::instance().logoAnimCounter;
|
return AppFireworks::getLogoAnimCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
int menu_getHoveredButton() {
|
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,
|
void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
|
||||||
|
|||||||
Reference in New Issue
Block a user