fixed i block

This commit is contained in:
2025-12-21 18:43:40 +01:00
parent 33d5eedec8
commit 0b99911f5d
2 changed files with 46 additions and 24 deletions

View File

@ -258,7 +258,8 @@ bool CoopGame::cellFilled(const Piece& p, int cx, int cy) {
const Shape& shape = SHAPES[p.type]; const Shape& shape = SHAPES[p.type];
uint16_t mask = shape[p.rot % 4]; uint16_t mask = shape[p.rot % 4];
int bitIndex = cy * 4 + cx; int bitIndex = cy * 4 + cx;
return (mask >> (15 - bitIndex)) & 1; // Masks are defined row-major 4x4 with bit 0 = (0,0) (same convention as classic).
return (mask >> bitIndex) & 1;
} }
void CoopGame::clearCompletedLines() { void CoopGame::clearCompletedLines() {

View File

@ -1864,7 +1864,7 @@ void GameRenderer::renderCoopPlayingState(
struct SmoothState { bool initialized{false}; uint64_t seq{0}; float visualX{0.0f}; float visualY{0.0f}; }; struct SmoothState { bool initialized{false}; uint64_t seq{0}; float visualX{0.0f}; float visualY{0.0f}; };
static SmoothState s_leftSmooth{}; static SmoothState s_leftSmooth{};
static SmoothState s_rightSmooth{}; static SmoothState s_rightSmooth{};
struct SpawnFadeState { bool active{false}; uint64_t seq{0}; Uint32 startTick{0}; float durationMs{200.0f}; CoopGame::Piece piece; float targetX{0.0f}; float targetY{0.0f}; float tileSize{0.0f}; }; struct SpawnFadeState { bool active{false}; uint64_t seq{0}; Uint32 startTick{0}; float durationMs{200.0f}; CoopGame::Piece piece; int spawnY{0}; float targetX{0.0f}; float targetY{0.0f}; float tileSize{0.0f}; };
static SpawnFadeState s_leftSpawnFade{}; static SpawnFadeState s_leftSpawnFade{};
static SpawnFadeState s_rightSpawnFade{}; static SpawnFadeState s_rightSpawnFade{};
@ -2281,11 +2281,15 @@ void GameRenderer::renderCoopPlayingState(
sf.durationMs = 200.0f; sf.durationMs = 200.0f;
sf.seq = seq; sf.seq = seq;
sf.piece = game->current(side); sf.piece = game->current(side);
sf.spawnY = sf.piece.y;
sf.tileSize = finalBlockSize; sf.tileSize = finalBlockSize;
// Note: targetX/targetY are recomputed during drawing using the live // Note: during the spawn fade we draw the live piece each frame.
// current piece so movement/rotation during the fade stays correct. // If the piece is still above the visible grid, we temporarily pin
sf.targetX = gridX + static_cast<float>(sf.piece.x) * finalBlockSize; // it so the topmost filled cell appears at row 0 (no spawn delay),
sf.targetY = gridY; // while still applying smoothing offsets so it starts moving
// immediately.
sf.targetX = 0.0f;
sf.targetY = 0.0f;
} else { } else {
// Reuse exact horizontal smoothing from single-player // Reuse exact horizontal smoothing from single-player
constexpr float HORIZONTAL_SMOOTH_MS = 55.0f; constexpr float HORIZONTAL_SMOOTH_MS = 55.0f;
@ -2358,10 +2362,7 @@ void GameRenderer::renderCoopPlayingState(
return std::pair<float, float>{ offsetX, offsetY }; return std::pair<float, float>{ offsetX, offsetY };
}; };
// Draw any active spawn fades (alpha ramp into first row). Draw before auto drawSpawnFadeIfActive = [&](SpawnFadeState &sf, CoopGame::PlayerSide side, const std::pair<float, float>& offsets) {
// the regular active pieces; while the spawn fade is active the piece's
// real position is above the grid and will not be drawn by drawPiece.
auto drawSpawnFadeIfActive = [&](SpawnFadeState &sf, CoopGame::PlayerSide side) {
if (!sf.active) return; if (!sf.active) return;
// If the piece has already changed, stop the fade. // If the piece has already changed, stop the fade.
@ -2377,36 +2378,56 @@ void GameRenderer::renderCoopPlayingState(
Uint8 alpha = static_cast<Uint8>(std::lround(255.0f * t)); Uint8 alpha = static_cast<Uint8>(std::lround(255.0f * t));
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, alpha); if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, alpha);
// Align the topmost filled cell to row 0 (first visible row), like classic.
int minCy = 4; int minCy = 4;
int maxCy = -1;
for (int cy = 0; cy < 4; ++cy) { for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) { for (int cx = 0; cx < 4; ++cx) {
if (!CoopGame::cellFilled(livePiece, cx, cy)) continue; if (!CoopGame::cellFilled(livePiece, cx, cy)) continue;
minCy = std::min(minCy, cy); minCy = std::min(minCy, cy);
maxCy = std::max(maxCy, cy);
} }
} }
if (minCy == 4) { if (minCy == 4) {
minCy = 0; minCy = 0;
} }
sf.targetX = gridX + static_cast<float>(livePiece.x) * sf.tileSize; if (maxCy < 0) {
sf.targetY = gridY - static_cast<float>(minCy) * sf.tileSize; maxCy = 0;
}
// Draw the live piece at the fade target. // Pin only when *no* filled cell is visible yet. Using maxCy avoids pinning
// cases like vertical I where some blocks are already visible at spawn.
const bool pinToFirstVisibleRow = (livePiece.y + maxCy) < 0;
const float baseX = gridX + static_cast<float>(livePiece.x) * sf.tileSize + offsets.first;
float baseY = 0.0f;
if (pinToFirstVisibleRow) {
// Keep the piece visible (topmost filled cell at row 0), but also
// incorporate real y-step progression so the fall accumulator wrapping
// doesn't produce a one-row snap.
const int dySteps = livePiece.y - sf.spawnY;
baseY = (gridY - static_cast<float>(minCy) * sf.tileSize)
+ static_cast<float>(dySteps) * sf.tileSize
+ offsets.second;
} else {
baseY = gridY + static_cast<float>(livePiece.y) * sf.tileSize + offsets.second;
}
// Draw the live piece (either pinned-to-row0 or at its real position).
for (int cy = 0; cy < 4; ++cy) { for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) { for (int cx = 0; cx < 4; ++cx) {
if (!CoopGame::cellFilled(livePiece, cx, cy)) continue; if (!CoopGame::cellFilled(livePiece, cx, cy)) continue;
float px = sf.targetX + static_cast<float>(cx) * sf.tileSize; int pyIdx = livePiece.y + cy;
float py = sf.targetY + static_cast<float>(cy) * sf.tileSize; if (!pinToFirstVisibleRow && pyIdx < 0) continue;
float px = baseX + static_cast<float>(cx) * sf.tileSize;
float py = baseY + static_cast<float>(cy) * sf.tileSize;
drawBlockTexturePublic(renderer, blocksTex, px, py, sf.tileSize, livePiece.type); drawBlockTexturePublic(renderer, blocksTex, px, py, sf.tileSize, livePiece.type);
} }
} }
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255); if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
// Critical: only deactivate once the real piece is actually drawable. // End fade after duration, but never stop while we are pinning (otherwise
// Otherwise (notably for I spawning at y=-2) there can be a brief gap where // I can briefly disappear until it becomes visible in the real grid).
// the fade ends but the real piece is still fully above the visible grid. if (t >= 1.0f && !pinToFirstVisibleRow) {
const bool pieceHasAnyVisibleCell = (livePiece.y + minCy) >= 0;
if (t >= 1.0f && pieceHasAnyVisibleCell) {
sf.active = false; sf.active = false;
} }
}; };
@ -2436,9 +2457,9 @@ void GameRenderer::renderCoopPlayingState(
}; };
const auto leftOffsets = computeOffsets(CoopGame::PlayerSide::Left, s_leftSmooth); const auto leftOffsets = computeOffsets(CoopGame::PlayerSide::Left, s_leftSmooth);
const auto rightOffsets = computeOffsets(CoopGame::PlayerSide::Right, s_rightSmooth); const auto rightOffsets = computeOffsets(CoopGame::PlayerSide::Right, s_rightSmooth);
// Draw transient spawn fades (if active) into the first visible row // Draw transient spawn fades (if active)
drawSpawnFadeIfActive(s_leftSpawnFade, CoopGame::PlayerSide::Left); drawSpawnFadeIfActive(s_leftSpawnFade, CoopGame::PlayerSide::Left, leftOffsets);
drawSpawnFadeIfActive(s_rightSpawnFade, CoopGame::PlayerSide::Right); drawSpawnFadeIfActive(s_rightSpawnFade, CoopGame::PlayerSide::Right, rightOffsets);
// Draw classic-style ghost pieces (landing position), grid-aligned. // Draw classic-style ghost pieces (landing position), grid-aligned.
// This intentionally does NOT use smoothing offsets. // This intentionally does NOT use smoothing offsets.