Add new level transition state machine

This commit is contained in:
2026-03-14 16:29:10 +00:00
parent 6d64c6426f
commit 7605f0ca8c
15 changed files with 615 additions and 55 deletions

View File

@@ -6,6 +6,7 @@
#include "game/editor.h"
#include "game/stats.h"
#include "game/analytics.h"
#include "game/transition.h"
#include "config.h"
#include <stdio.h>
#include <string.h>
@@ -24,6 +25,7 @@ typedef enum GameMode {
MODE_PLAY,
MODE_EDITOR,
MODE_PAUSED,
MODE_TRANSITION,
} GameMode;
static Level s_level;
@@ -56,6 +58,10 @@ static bool s_session_active = false;
#define PAUSE_ITEM_COUNT 3
static int s_pause_selection = 0; /* 0=Resume, 1=Restart, 2=Quit */
/* ── Level transition state ── */
static TransitionState s_transition;
static char s_pending_target[ASSET_PATH_MAX] = {0}; /* exit target stashed during transition */
#ifdef __EMSCRIPTEN__
/* JS-initiated level load request (level-select dropdown in shell). */
static int s_js_load_request = 0;
@@ -321,6 +327,48 @@ static void restart_level(void) {
}
}
/* ── Level load dispatch — loads the next level based on target string ── */
static void dispatch_level_load(const char *target) {
if (target[0] == '\0') {
/* Empty target = victory / end of game. */
printf("Level complete! (no next level)\n");
end_session("completed");
level_free(&s_level);
s_station_depth = 0;
s_mars_depth = 0;
if (!load_level_file("assets/levels/moon01.lvl")) {
g_engine.running = false;
}
begin_session();
} else if (strcmp(target, "generate") == 0) {
printf("Transitioning to generated level\n");
level_free(&s_level);
s_gen_seed = (uint32_t)time(NULL);
load_generated_level();
} else if (strcmp(target, "generate:station") == 0) {
printf("Transitioning to space station level\n");
level_free(&s_level);
s_gen_seed = (uint32_t)time(NULL);
load_station_level();
} else if (strcmp(target, "generate:mars_base") == 0) {
printf("Transitioning to Mars Base level\n");
level_free(&s_level);
s_gen_seed = (uint32_t)time(NULL);
load_mars_base_level();
} else {
printf("Transitioning to: %s\n", target);
char path[ASSET_PATH_MAX];
snprintf(path, sizeof(path), "%s", target);
level_free(&s_level);
if (!load_level_file(path)) {
fprintf(stderr, "Failed to load next level: %s\n", path);
if (!load_level_file("assets/levels/moon01.lvl")) {
g_engine.running = false;
}
}
}
}
/* ═══════════════════════════════════════════════════
* Game callbacks
* ═══════════════════════════════════════════════════ */
@@ -395,7 +443,9 @@ static void game_update(float dt) {
end_session("quit");
/* Tear down whatever mode we are in. */
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED) {
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED
|| s_mode == MODE_TRANSITION) {
transition_reset(&s_transition);
level_free(&s_level);
} else if (s_mode == MODE_EDITOR) {
editor_free(&s_editor);
@@ -440,6 +490,28 @@ static void game_update(float dt) {
return;
}
if (s_mode == MODE_TRANSITION) {
transition_update(&s_transition, dt, &s_level.camera);
/* Outro finished — swap levels. */
if (transition_needs_load(&s_transition)) {
dispatch_level_load(s_pending_target);
s_pending_target[0] = '\0';
/* Use the new level's intro style. */
TransitionStyle in_style = s_level.map.transition_in;
transition_set_in_style(&s_transition, in_style);
transition_begin_intro(&s_transition);
}
/* Intro finished — return to play. */
if (transition_is_done(&s_transition)) {
transition_reset(&s_transition);
s_mode = MODE_PLAY;
}
return;
}
/* ── Play mode ── */
/* Pause on escape (return to editor during test play) */
@@ -492,49 +564,16 @@ static void game_update(float dt) {
s_stats.levels_completed++;
}
if (target[0] == '\0') {
/* Empty target = victory / end of game */
printf("Level complete! (no next level)\n");
end_session("completed");
/* Loop back to the beginning, reset progression state */
level_free(&s_level);
s_station_depth = 0;
s_mars_depth = 0;
if (!load_level_file("assets/levels/moon01.lvl")) {
g_engine.running = false;
}
begin_session();
} else if (strcmp(target, "generate") == 0) {
/* Procedurally generated next level */
printf("Transitioning to generated level\n");
level_free(&s_level);
s_gen_seed = (uint32_t)time(NULL);
load_generated_level();
} else if (strcmp(target, "generate:station") == 0) {
/* Procedurally generated space station level */
printf("Transitioning to space station level\n");
level_free(&s_level);
s_gen_seed = (uint32_t)time(NULL);
load_station_level();
} else if (strcmp(target, "generate:mars_base") == 0) {
/* Procedurally generated Mars Base level */
printf("Transitioning to Mars Base level\n");
level_free(&s_level);
s_gen_seed = (uint32_t)time(NULL);
load_mars_base_level();
TransitionStyle out_style = s_level.map.transition_out;
if (out_style == TRANS_ELEVATOR || out_style == TRANS_TELEPORTER) {
/* Animated transition: stash target, start outro. */
snprintf(s_pending_target, sizeof(s_pending_target), "%s", target);
transition_start_out(&s_transition, out_style);
s_mode = MODE_TRANSITION;
} else {
/* Load a specific level file */
printf("Transitioning to: %s\n", target);
char path[ASSET_PATH_MAX];
snprintf(path, sizeof(path), "%s", target);
level_free(&s_level);
if (!load_level_file(path)) {
fprintf(stderr, "Failed to load next level: %s\n", path);
/* Fallback to moon01 */
if (!load_level_file("assets/levels/moon01.lvl")) {
g_engine.running = false;
}
}
/* Instant transition (none or spacecraft-driven). */
dispatch_level_load(target);
}
}
}
@@ -587,6 +626,10 @@ static void game_render(float interpolation) {
/* Render frozen game frame, then overlay the pause menu. */
level_render(&s_level, interpolation);
pause_render();
} else if (s_mode == MODE_TRANSITION) {
/* Render the level (frozen) with the transition overlay on top. */
level_render(&s_level, interpolation);
transition_render(&s_transition);
} else {
level_render(&s_level, interpolation);
}
@@ -598,7 +641,8 @@ static void game_shutdown(void) {
/* Always free both — editor may have been initialized even if we're
* currently in play mode (e.g. shutdown during test play). editor_free
* and level_free are safe to call on zeroed/already-freed structs. */
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED || s_testing_from_editor) {
if (s_mode == MODE_PLAY || s_mode == MODE_PAUSED
|| s_mode == MODE_TRANSITION || s_testing_from_editor) {
level_free(&s_level);
}
if (s_mode == MODE_EDITOR || s_use_editor) {