diff --git a/assets/music/48997.wav b/assets/music/48997.wav deleted file mode 100644 index 4ea7d20..0000000 Binary files a/assets/music/48997.wav and /dev/null differ diff --git a/assets/music/GONG0.WAV b/assets/music/GONG0.WAV deleted file mode 100644 index 3a54e08..0000000 Binary files a/assets/music/GONG0.WAV and /dev/null differ diff --git a/src/app/TetrisApp.cpp b/src/app/TetrisApp.cpp index c3f96ff..0b0db60 100644 --- a/src/app/TetrisApp.cpp +++ b/src/app/TetrisApp.cpp @@ -267,6 +267,7 @@ struct TetrisApp::Impl { int countdownLevel = 0; int countdownGoalAsteroids = 0; bool countdownAdvancesChallenge = false; + bool challengeCountdownWaitingForSpace = false; double gameplayBackgroundClockMs = 0.0; std::string challengeStoryText; int challengeStoryLevel = 0; @@ -1001,6 +1002,28 @@ void TetrisApp::Impl::runLoop() } } } + else if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) { + if (gameplayCountdownActive && gameplayCountdownSource == CountdownSource::ChallengeLevel && challengeCountdownWaitingForSpace) { + if (e.key.scancode == SDL_SCANCODE_SPACE) { + challengeCountdownWaitingForSpace = false; + gameplayCountdownElapsed = 0.0; + gameplayCountdownIndex = 0; + } else if (e.key.scancode == SDL_SCANCODE_ESCAPE) { + // Show quit popup, keep game paused, cancel countdown + if (!showExitConfirmPopup) { + showExitConfirmPopup = true; + exitPopupSelectedButton = 1; // default to NO + } + gameplayCountdownActive = false; + menuPlayCountdownArmed = false; + gameplayCountdownElapsed = 0.0; + gameplayCountdownIndex = 0; + countdownAdvancesChallenge = false; + challengeCountdownWaitingForSpace = false; + if (game) game->setPaused(true); + } + } + } } } @@ -1017,24 +1040,29 @@ void TetrisApp::Impl::runLoop() challengeStoryClockMs = 0.0; }; - // Update challenge story fade/timeout + // Update challenge story fade/timeout; during countdown wait we keep it fully visible if (state == AppState::Playing && game && game->getMode() == GameMode::Challenge && !challengeStoryText.empty()) { - const double fadeInMs = 320.0; - const double holdMs = 3200.0; - const double fadeOutMs = 900.0; - const double totalMs = fadeInMs + holdMs + fadeOutMs; - challengeStoryClockMs += frameMs; - if (challengeStoryClockMs >= totalMs) { - clearChallengeStory(); + if (gameplayCountdownActive && gameplayCountdownSource == CountdownSource::ChallengeLevel && challengeCountdownWaitingForSpace) { + // Locked-visible while waiting + challengeStoryAlpha = 1.0f; } else { - double a = 1.0; - if (challengeStoryClockMs < fadeInMs) { - a = challengeStoryClockMs / fadeInMs; - } else if (challengeStoryClockMs > fadeInMs + holdMs) { - double t = challengeStoryClockMs - (fadeInMs + holdMs); - a = std::max(0.0, 1.0 - t / fadeOutMs); + const double fadeInMs = 320.0; + const double holdMs = 3200.0; + const double fadeOutMs = 900.0; + const double totalMs = fadeInMs + holdMs + fadeOutMs; + challengeStoryClockMs += frameMs; + if (challengeStoryClockMs >= totalMs) { + clearChallengeStory(); + } else { + double a = 1.0; + if (challengeStoryClockMs < fadeInMs) { + a = challengeStoryClockMs / fadeInMs; + } else if (challengeStoryClockMs > fadeInMs + holdMs) { + double t = challengeStoryClockMs - (fadeInMs + holdMs); + a = std::max(0.0, 1.0 - t / fadeOutMs); + } + challengeStoryAlpha = static_cast(std::clamp(a, 0.0, 1.0)); } - challengeStoryAlpha = static_cast(std::clamp(a, 0.0, 1.0)); } } else { clearChallengeStory(); @@ -1057,6 +1085,7 @@ void TetrisApp::Impl::runLoop() captureChallengeStory(countdownLevel); countdownAdvancesChallenge = false; // already advanced gameplayCountdownActive = true; + challengeCountdownWaitingForSpace = true; menuPlayCountdownArmed = false; gameplayCountdownElapsed = 0.0; gameplayCountdownIndex = 0; @@ -1460,9 +1489,11 @@ void TetrisApp::Impl::runLoop() countdownGoalAsteroids = countdownLevel; if (gameplayCountdownSource == CountdownSource::ChallengeLevel) { captureChallengeStory(countdownLevel); + challengeCountdownWaitingForSpace = true; } else { challengeStoryText.clear(); challengeStoryLevel = 0; + challengeCountdownWaitingForSpace = false; } countdownAdvancesChallenge = false; menuPlayCountdownArmed = true; @@ -1475,6 +1506,7 @@ void TetrisApp::Impl::runLoop() gameplayCountdownActive = false; gameplayCountdownIndex = 0; gameplayCountdownElapsed = 0.0; + challengeCountdownWaitingForSpace = false; game->setPaused(false); } menuFadePhase = MenuFadePhase::FadeIn; @@ -1499,9 +1531,11 @@ void TetrisApp::Impl::runLoop() countdownGoalAsteroids = countdownLevel; if (gameplayCountdownSource == CountdownSource::ChallengeLevel) { captureChallengeStory(countdownLevel); + challengeCountdownWaitingForSpace = true; } else { challengeStoryText.clear(); challengeStoryLevel = 0; + challengeCountdownWaitingForSpace = false; } countdownAdvancesChallenge = false; gameplayCountdownActive = true; @@ -1512,19 +1546,21 @@ void TetrisApp::Impl::runLoop() } if (gameplayCountdownActive && state == AppState::Playing) { - gameplayCountdownElapsed += frameMs; - if (gameplayCountdownElapsed >= GAMEPLAY_COUNTDOWN_STEP_MS) { - gameplayCountdownElapsed -= GAMEPLAY_COUNTDOWN_STEP_MS; - ++gameplayCountdownIndex; - if (gameplayCountdownIndex >= static_cast(GAMEPLAY_COUNTDOWN_LABELS.size())) { - gameplayCountdownActive = false; - gameplayCountdownElapsed = 0.0; - gameplayCountdownIndex = 0; - if (gameplayCountdownSource == CountdownSource::ChallengeLevel && countdownAdvancesChallenge && game) { - game->beginNextChallengeLevel(); + if (!challengeCountdownWaitingForSpace || gameplayCountdownSource != CountdownSource::ChallengeLevel) { + gameplayCountdownElapsed += frameMs; + if (gameplayCountdownElapsed >= GAMEPLAY_COUNTDOWN_STEP_MS) { + gameplayCountdownElapsed -= GAMEPLAY_COUNTDOWN_STEP_MS; + ++gameplayCountdownIndex; + if (gameplayCountdownIndex >= static_cast(GAMEPLAY_COUNTDOWN_LABELS.size())) { + gameplayCountdownActive = false; + gameplayCountdownElapsed = 0.0; + gameplayCountdownIndex = 0; + if (gameplayCountdownSource == CountdownSource::ChallengeLevel && countdownAdvancesChallenge && game) { + game->beginNextChallengeLevel(); + } + countdownAdvancesChallenge = false; + game->setPaused(false); } - countdownAdvancesChallenge = false; - game->setPaused(false); } } } @@ -1535,6 +1571,7 @@ void TetrisApp::Impl::runLoop() gameplayCountdownElapsed = 0.0; gameplayCountdownIndex = 0; countdownAdvancesChallenge = false; + challengeCountdownWaitingForSpace = false; game->setPaused(false); } @@ -1953,10 +1990,82 @@ void TetrisApp::Impl::runLoop() float goalY = levelY + static_cast(lvlH) + 14.0f; pixelFont.draw(renderer, goalX, goalY, goalBuf, goalScale, SDL_Color{220, 245, 255, 255}); - textY = goalY + static_cast(goalH) + 38.0f; + // Optional story/briefing line + if (!challengeStoryText.empty() && challengeStoryAlpha > 0.0f) { + SDL_Color storyColor{170, 230, 255, static_cast(std::lround(255.0f * challengeStoryAlpha))}; + SDL_Color shadowColor{0, 0, 0, static_cast(std::lround(160.0f * challengeStoryAlpha))}; + + auto drawCenteredWrapped = [&](const std::string& text, float y, float maxWidth, float scale) { + std::istringstream iss(text); + std::string word; + std::string line; + float cursorY = y; + int lastH = 0; + while (iss >> word) { + std::string candidate = line.empty() ? word : (line + " " + word); + int candidateW = 0, candidateH = 0; + pixelFont.measure(candidate, scale, candidateW, candidateH); + if (candidateW > maxWidth && !line.empty()) { + int lineW = 0, lineH = 0; + pixelFont.measure(line, scale, lineW, lineH); + float lineX = (winW - static_cast(lineW)) * 0.5f; + pixelFont.draw(renderer, lineX + 1.0f, cursorY + 1.0f, line, scale, shadowColor); + pixelFont.draw(renderer, lineX, cursorY, line, scale, storyColor); + cursorY += lineH + 6.0f; + line = word; + lastH = lineH; + } else { + line = candidate; + lastH = candidateH; + } + } + if (!line.empty()) { + int w = 0, h = 0; + pixelFont.measure(line, scale, w, h); + float lineX = (winW - static_cast(w)) * 0.5f; + pixelFont.draw(renderer, lineX + 1.0f, cursorY + 1.0f, line, scale, shadowColor); + pixelFont.draw(renderer, lineX, cursorY, line, scale, storyColor); + cursorY += h + 6.0f; + } + return cursorY; + }; + + float storyStartY = goalY + static_cast(goalH) + 22.0f; + float usedY = drawCenteredWrapped(challengeStoryText, storyStartY, std::min(winW * 0.7f, 720.0f), 1.0f); + float promptY = usedY + 10.0f; + if (challengeCountdownWaitingForSpace) { + const char* prompt = "PRESS SPACE"; + int pW = 0, pH = 0; + float pScale = 1.35f; + pixelFont.measure(prompt, pScale, pW, pH); + float px = (winW - static_cast(pW)) * 0.5f; + pixelFont.draw(renderer, px + 2.0f, promptY + 2.0f, prompt, pScale, SDL_Color{0, 0, 0, 200}); + pixelFont.draw(renderer, px, promptY, prompt, pScale, SDL_Color{255, 220, 40, 255}); + promptY += pH + 14.0f; + } + textY = promptY + 10.0f; + } else { + if (challengeCountdownWaitingForSpace) { + const char* prompt = "PRESS SPACE"; + int pW = 0, pH = 0; + float pScale = 1.35f; + pixelFont.measure(prompt, pScale, pW, pH); + float px = (winW - static_cast(pW)) * 0.5f; + float py = goalY + static_cast(goalH) + 18.0f; + pixelFont.draw(renderer, px + 2.0f, py + 2.0f, prompt, pScale, SDL_Color{0, 0, 0, 200}); + pixelFont.draw(renderer, px, py, prompt, pScale, SDL_Color{255, 220, 40, 255}); + textY = py + pH + 24.0f; + } else { + textY = goalY + static_cast(goalH) + 38.0f; + } + } + } else { + textY = winH * 0.38f; + } + if (!(gameplayCountdownSource == CountdownSource::ChallengeLevel && challengeCountdownWaitingForSpace)) { + SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255}; + pixelFont.draw(renderer, textX, textY, label, textScale, textColor); } - SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255}; - pixelFont.draw(renderer, textX, textY, label, textScale, textColor); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); } diff --git a/src/audio/SoundEffect.cpp b/src/audio/SoundEffect.cpp index 2a34b66..ecc34ee 100644 --- a/src/audio/SoundEffect.cpp +++ b/src/audio/SoundEffect.cpp @@ -46,7 +46,6 @@ bool SoundEffect::load(const std::string& filePath) { } loaded = true; - //std::printf("[SoundEffect] Loaded: %s (%d channels, %d Hz, %zu samples)\n", filePath.c_str(), channels, sampleRate, pcmData.size()); return true; } @@ -55,9 +54,7 @@ void SoundEffect::play(float volume) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Cannot play - loaded=%d, pcmData.size()=%zu", loaded, pcmData.size()); return; } - - //std::printf("[SoundEffect] Playing sound with %zu samples at volume %.2f\n", pcmData.size(), volume); - + // Calculate final volume float finalVolume = defaultVolume * volume; finalVolume = (std::max)(0.0f, (std::min)(1.0f, finalVolume)); diff --git a/src/states/PlayingState.cpp b/src/states/PlayingState.cpp index 271c2ee..21fee29 100644 --- a/src/states/PlayingState.cpp +++ b/src/states/PlayingState.cpp @@ -270,8 +270,8 @@ void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l challengeClearOrder, challengeClearElapsed, challengeClearDuration, - ctx.challengeStoryText, - ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f + countdown ? nullptr : ctx.challengeStoryText, + countdown ? 0.0f : (ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f) ); // Reset to screen @@ -366,8 +366,8 @@ void PlayingState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l challengeClearOrder, challengeClearElapsed, challengeClearDuration, - ctx.challengeStoryText, - ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f + countdown ? nullptr : ctx.challengeStoryText, + countdown ? 0.0f : (ctx.challengeStoryAlpha ? *ctx.challengeStoryAlpha : 0.0f) ); } }