added challenge level text
This commit is contained in:
@ -82,6 +82,75 @@ static const std::array<SDL_Color, PIECE_COUNT + 1> COLORS = {{
|
||||
SDL_Color{255, 160, 0, 255}, // L
|
||||
}};
|
||||
|
||||
static std::string GetLevelStoryText(int level) {
|
||||
int lvl = std::clamp(level, 1, 100);
|
||||
|
||||
// Milestones
|
||||
switch (lvl) {
|
||||
case 1: return "Launch log: training run, light debris ahead.";
|
||||
case 25: return "Checkpoint: dense field reported, shields ready.";
|
||||
case 50: return "Midway brief: hull stress rising, stay sharp.";
|
||||
case 75: return "Emergency corridor: comms unstable, proceed blind.";
|
||||
case 100: return "Final anomaly: unknown mass ahead, hold course.";
|
||||
default: break;
|
||||
}
|
||||
|
||||
struct Pool { int minL, maxL; std::vector<std::string> lines; };
|
||||
static const std::vector<Pool> pools = {
|
||||
{1, 10, {
|
||||
"Departure logged: light debris, stay on vector.",
|
||||
"Training sector: minimal drift, keep sensors warm.",
|
||||
"Calm approach: verify thrusters and nav locks.",
|
||||
"Outer ring dust: watch for slow movers.",
|
||||
"Clear lanes ahead: focus on smooth rotations."
|
||||
}},
|
||||
{11, 25, {
|
||||
"Asteroid belt thickening; micro-impacts likely.",
|
||||
"Density rising: plot short burns only.",
|
||||
"Field report: medium fragments, unpredictable spin.",
|
||||
"Warning: overlapping paths, reduce horizontal drift.",
|
||||
"Rock chorus ahead; keep payload stable."
|
||||
}},
|
||||
{26, 40, {
|
||||
"Unstable sector: abandoned relays drifting erratic.",
|
||||
"Salvage echoes detected; debris wakes may tug.",
|
||||
"Hull groans recorded; inert structures nearby.",
|
||||
"Navigation buoys dark; trust instruments only.",
|
||||
"Magnetic static rising; expect odd rotations."
|
||||
}},
|
||||
{41, 60, {
|
||||
"Core corridor: heavy asteroids, minimal clearance.",
|
||||
"Impact risk high: armor checks recommended.",
|
||||
"Dense stone flow; time burns carefully.",
|
||||
"Grav eddies noted; blocks may drift late.",
|
||||
"Core shards are brittle; expect sudden splits."
|
||||
}},
|
||||
{61, 80, {
|
||||
"Critical zone: alarms pinned, route unstable.",
|
||||
"Emergency pattern: glide, then cut thrust.",
|
||||
"Sensors flare; debris ionized, visibility low.",
|
||||
"Thermals spiking; keep pieces tight and fast.",
|
||||
"Silent channel; assume worst-case collision."
|
||||
}},
|
||||
{81, 100, {
|
||||
"Unknown space: signals warp, gravity unreliable.",
|
||||
"Anomaly bloom ahead; shapes flicker unpredictably.",
|
||||
"Final drift: void sings through hull plates.",
|
||||
"Black sector: map useless, fly by instinct.",
|
||||
"Edge of chart: nothing responds, just move."
|
||||
}}
|
||||
};
|
||||
|
||||
for (const auto& pool : pools) {
|
||||
if (lvl >= pool.minL && lvl <= pool.maxL && !pool.lines.empty()) {
|
||||
size_t idx = static_cast<size_t>((lvl - pool.minL) % pool.lines.size());
|
||||
return pool.lines[idx];
|
||||
}
|
||||
}
|
||||
|
||||
return "Mission log update unavailable.";
|
||||
}
|
||||
|
||||
struct TetrisApp::Impl {
|
||||
// Global collector for asset loading errors shown on the loading screen
|
||||
std::vector<std::string> assetLoadErrors;
|
||||
@ -199,6 +268,10 @@ struct TetrisApp::Impl {
|
||||
int countdownGoalAsteroids = 0;
|
||||
bool countdownAdvancesChallenge = false;
|
||||
double gameplayBackgroundClockMs = 0.0;
|
||||
std::string challengeStoryText;
|
||||
int challengeStoryLevel = 0;
|
||||
float challengeStoryAlpha = 0.0f;
|
||||
double challengeStoryClockMs = 0.0;
|
||||
|
||||
// Challenge clear FX (celebratory board explosion before countdown)
|
||||
bool challengeClearFxActive = false;
|
||||
@ -465,6 +538,9 @@ int TetrisApp::Impl::init()
|
||||
ctx.challengeClearFxElapsedMs = &challengeClearFxElapsedMs;
|
||||
ctx.challengeClearFxDurationMs = &challengeClearFxDurationMs;
|
||||
ctx.challengeClearFxOrder = &challengeClearFxOrder;
|
||||
ctx.challengeStoryText = &challengeStoryText;
|
||||
ctx.challengeStoryLevel = &challengeStoryLevel;
|
||||
ctx.challengeStoryAlpha = &challengeStoryAlpha;
|
||||
ctx.playerName = &playerName;
|
||||
ctx.fullscreenFlag = &isFullscreen;
|
||||
ctx.applyFullscreen = [this](bool enable) {
|
||||
@ -585,6 +661,14 @@ void TetrisApp::Impl::runLoop()
|
||||
}
|
||||
};
|
||||
|
||||
auto captureChallengeStory = [this](int level) {
|
||||
int lvl = std::clamp(level, 1, 100);
|
||||
challengeStoryLevel = lvl;
|
||||
challengeStoryText = GetLevelStoryText(lvl);
|
||||
challengeStoryClockMs = 0.0;
|
||||
challengeStoryAlpha = 0.0f;
|
||||
};
|
||||
|
||||
auto startChallengeClearFx = [this](int nextLevel) {
|
||||
challengeClearFxOrder.clear();
|
||||
const auto& boardRef = game->boardRef();
|
||||
@ -926,6 +1010,36 @@ void TetrisApp::Impl::runLoop()
|
||||
if (frameMs > 100.0) frameMs = 100.0;
|
||||
gameplayBackgroundClockMs += frameMs;
|
||||
|
||||
auto clearChallengeStory = [this]() {
|
||||
challengeStoryText.clear();
|
||||
challengeStoryLevel = 0;
|
||||
challengeStoryAlpha = 0.0f;
|
||||
challengeStoryClockMs = 0.0;
|
||||
};
|
||||
|
||||
// Update challenge story fade/timeout
|
||||
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();
|
||||
} 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<float>(std::clamp(a, 0.0, 1.0));
|
||||
}
|
||||
} else {
|
||||
clearChallengeStory();
|
||||
}
|
||||
|
||||
if (challengeClearFxActive) {
|
||||
challengeClearFxElapsedMs += frameMs;
|
||||
if (challengeClearFxElapsedMs >= challengeClearFxDurationMs) {
|
||||
@ -940,6 +1054,7 @@ void TetrisApp::Impl::runLoop()
|
||||
gameplayCountdownSource = CountdownSource::ChallengeLevel;
|
||||
countdownLevel = challengeClearFxNextLevel;
|
||||
countdownGoalAsteroids = challengeClearFxNextLevel;
|
||||
captureChallengeStory(countdownLevel);
|
||||
countdownAdvancesChallenge = false; // already advanced
|
||||
gameplayCountdownActive = true;
|
||||
menuPlayCountdownArmed = false;
|
||||
@ -1343,6 +1458,12 @@ void TetrisApp::Impl::runLoop()
|
||||
: CountdownSource::MenuStart;
|
||||
countdownLevel = game ? game->challengeLevel() : 1;
|
||||
countdownGoalAsteroids = countdownLevel;
|
||||
if (gameplayCountdownSource == CountdownSource::ChallengeLevel) {
|
||||
captureChallengeStory(countdownLevel);
|
||||
} else {
|
||||
challengeStoryText.clear();
|
||||
challengeStoryLevel = 0;
|
||||
}
|
||||
countdownAdvancesChallenge = false;
|
||||
menuPlayCountdownArmed = true;
|
||||
gameplayCountdownActive = false;
|
||||
@ -1376,6 +1497,12 @@ void TetrisApp::Impl::runLoop()
|
||||
: CountdownSource::MenuStart;
|
||||
countdownLevel = game ? game->challengeLevel() : 1;
|
||||
countdownGoalAsteroids = countdownLevel;
|
||||
if (gameplayCountdownSource == CountdownSource::ChallengeLevel) {
|
||||
captureChallengeStory(countdownLevel);
|
||||
} else {
|
||||
challengeStoryText.clear();
|
||||
challengeStoryLevel = 0;
|
||||
}
|
||||
countdownAdvancesChallenge = false;
|
||||
gameplayCountdownActive = true;
|
||||
menuPlayCountdownArmed = false;
|
||||
|
||||
Reference in New Issue
Block a user