From cf3e8977528a3970ea7edfc50d222782b8112e76 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sun, 21 Dec 2025 16:25:09 +0100 Subject: [PATCH] added clear line effect --- src/gameplay/effects/LineEffect.cpp | 7 +++--- src/gameplay/effects/LineEffect.h | 3 ++- src/graphics/renderers/GameRenderer.cpp | 31 ++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/gameplay/effects/LineEffect.cpp b/src/gameplay/effects/LineEffect.cpp index e918210..0dc3570 100644 --- a/src/gameplay/effects/LineEffect.cpp +++ b/src/gameplay/effects/LineEffect.cpp @@ -188,10 +188,11 @@ void LineEffect::initAudio() { } } -void LineEffect::startLineClear(const std::vector& rows, int gridX, int gridY, int blockSize) { +void LineEffect::startLineClear(const std::vector& rows, int gridX, int gridY, int blockSize, int gridCols) { if (rows.empty()) return; clearingRows = rows; + effectGridCols = std::max(1, gridCols); state = AnimationState::FLASH_WHITE; timer = 0.0f; dropProgress = 0.0f; @@ -228,7 +229,7 @@ void LineEffect::startLineClear(const std::vector& rows, int gridX, int gri void LineEffect::createParticles(int row, int gridX, int gridY, int blockSize) { const float centerY = gridY + row * blockSize + blockSize * 0.5f; - for (int col = 0; col < Game::COLS; ++col) { + for (int col = 0; col < effectGridCols; ++col) { float centerX = gridX + col * blockSize + blockSize * 0.5f; SDL_Color tint = pickFireColor(); spawnGlowPulse(centerX, centerY, static_cast(blockSize), tint); @@ -386,7 +387,7 @@ void LineEffect::renderFlash(int gridX, int gridY, int blockSize) { SDL_FRect flashRect = { static_cast(gridX - 4), static_cast(gridY + row * blockSize - 4), - static_cast(10 * blockSize + 8), + static_cast(effectGridCols * blockSize + 8), static_cast(blockSize + 8) }; SDL_RenderFillRect(renderer, &flashRect); diff --git a/src/gameplay/effects/LineEffect.h b/src/gameplay/effects/LineEffect.h index 99834ab..26fea94 100644 --- a/src/gameplay/effects/LineEffect.h +++ b/src/gameplay/effects/LineEffect.h @@ -69,7 +69,7 @@ public: void shutdown(); // Start line clear effect for the specified rows - void startLineClear(const std::vector& rows, int gridX, int gridY, int blockSize); + void startLineClear(const std::vector& rows, int gridX, int gridY, int blockSize, int gridCols = Game::COLS); // Update and render the effect bool update(float deltaTime); // Returns true if effect is complete @@ -120,4 +120,5 @@ private: std::array rowDropTargets{}; float dropProgress = 0.0f; int dropBlockSize = 0; + int effectGridCols = Game::COLS; }; diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index 57d9dbe..3b55de9 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -1926,12 +1926,18 @@ void GameRenderer::renderCoopPlayingState( // Handle line clearing effects (defer to LineEffect like single-player) if (game->hasCompletedLines() && lineEffect && !lineEffect->isActive()) { auto completedLines = game->getCompletedLines(); - lineEffect->startLineClear(completedLines, static_cast(gridX), static_cast(gridY), static_cast(finalBlockSize)); + lineEffect->startLineClear(completedLines, static_cast(gridX), static_cast(gridY), static_cast(finalBlockSize), CoopGame::COLS); if (completedLines.size() == 4) { AppFireworks::spawn(gridX + GRID_W * 0.5f, gridY + GRID_H * 0.5f); } } + // Precompute row drop offsets (line collapse effect) + std::array rowDropOffsets{}; + for (int y = 0; y < CoopGame::ROWS; ++y) { + rowDropOffsets[y] = (lineEffect ? lineEffect->getRowDropOffset(y) : 0.0f); + } + // Grid backdrop and border drawRectWithOffset(gridX - 3 - contentOffsetX, gridY - 3 - contentOffsetY, GRID_W + 6, GRID_H + 6, {100, 120, 200, 255}); drawRectWithOffset(gridX - contentOffsetX, gridY - contentOffsetY, GRID_W, GRID_H, {20, 25, 35, 255}); @@ -1980,11 +1986,12 @@ void GameRenderer::renderCoopPlayingState( // Draw settled blocks const auto& board = game->boardRef(); for (int y = 0; y < CoopGame::ROWS; ++y) { + float dropOffset = rowDropOffsets[y]; for (int x = 0; x < CoopGame::COLS; ++x) { const auto& cell = board[y * CoopGame::COLS + x]; if (!cell.occupied || cell.value <= 0) continue; float px = gridX + x * finalBlockSize; - float py = gridY + y * finalBlockSize; + float py = gridY + y * finalBlockSize + dropOffset; drawBlockTexturePublic(renderer, blocksTex, px, py, finalBlockSize, cell.value - 1); } } @@ -2012,7 +2019,20 @@ void GameRenderer::renderCoopPlayingState( sf.tileSize = finalBlockSize; // Target to first visible row (row 0) sf.targetX = gridX + static_cast(sf.piece.x) * finalBlockSize; - sf.targetY = gridY + 0.0f * finalBlockSize; + // IMPORTANT: In classic mode, pieces can spawn with their first filled + // cell not at cy=0 within the 4x4. To avoid appearing one row too low + // (and then jumping up), align the topmost filled cell to row 0. + int minCy = 4; + for (int cy = 0; cy < 4; ++cy) { + for (int cx = 0; cx < 4; ++cx) { + if (!CoopGame::cellFilled(sf.piece, cx, cy)) continue; + minCy = std::min(minCy, cy); + } + } + if (minCy == 4) { + minCy = 0; + } + sf.targetY = gridY - static_cast(minCy) * finalBlockSize; } else { // Reuse exact horizontal smoothing from single-player constexpr float HORIZONTAL_SMOOTH_MS = 55.0f; @@ -2177,6 +2197,11 @@ void GameRenderer::renderCoopPlayingState( drawPiece(game->current(CoopGame::PlayerSide::Right), rightOffsets, false); } + // Draw line clearing effects above pieces (matches single-player) + if (lineEffect && lineEffect->isActive()) { + lineEffect->render(renderer, blocksTex, static_cast(gridX), static_cast(gridY), static_cast(finalBlockSize)); + } + // Next panels (two) const float nextPanelPad = 12.0f; const float nextPanelW = (GRID_W * 0.5f) - finalBlockSize * 1.5f;