forked from tas/major_tom
Add spacecraft exit sequence at level exit zones
Spawn an exit ship when the player approaches an exit zone. The ship flies in, lands near the exit, and waits for the player to board. On boarding the player is deactivated, the ship takes off, and the level transition fires after departure.
This commit is contained in:
168
src/game/level.c
168
src/game/level.c
@@ -360,21 +360,121 @@ static void handle_collisions(EntityManager *em) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Exit zone checking ──────────────────────────── */
|
||||
/* ── Exit zone / exit ship ───────────────────────── */
|
||||
|
||||
static void check_exit_zones(Level *level) {
|
||||
/* How close the player must be to an exit zone to trigger the exit ship
|
||||
* fly-in. Roughly 2 screen widths. */
|
||||
#define EXIT_SHIP_TRIGGER_DIST (SCREEN_WIDTH * 2.0f)
|
||||
|
||||
/* Landing offset: where the ship lands relative to the exit zone center.
|
||||
* The ship lands a bit to the left of the exit zone so the player walks
|
||||
* rightward into it. */
|
||||
#define EXIT_SHIP_LAND_OFFSET_X (-SPACECRAFT_WIDTH * 0.5f)
|
||||
|
||||
/* Find the active, alive player entity (or NULL). */
|
||||
static Entity *find_player(EntityManager *em) {
|
||||
for (int i = 0; i < em->count; i++) {
|
||||
Entity *e = &em->entities[i];
|
||||
if (e->active && e->type == ENT_PLAYER && !(e->flags & ENTITY_DEAD))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the exit spacecraft entity (or NULL). */
|
||||
static Entity *find_exit_ship(EntityManager *em) {
|
||||
for (int i = 0; i < em->count; i++) {
|
||||
Entity *e = &em->entities[i];
|
||||
if (e->active && e->type == ENT_SPACECRAFT && spacecraft_is_exit_ship(e))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Spawn the exit ship when the player gets close to an exit zone. */
|
||||
static void check_exit_ship_proximity(Level *level) {
|
||||
if (level->exit_triggered) return;
|
||||
if (level->exit_ship_spawned) return;
|
||||
if (level->map.exit_zone_count == 0) return;
|
||||
|
||||
/* Find the player */
|
||||
Entity *player = NULL;
|
||||
for (int i = 0; i < level->entities.count; i++) {
|
||||
Entity *e = &level->entities.entities[i];
|
||||
if (e->active && e->type == ENT_PLAYER && !(e->flags & ENTITY_DEAD)) {
|
||||
player = e;
|
||||
break;
|
||||
Entity *player = find_player(&level->entities);
|
||||
if (!player) return;
|
||||
|
||||
Vec2 pc = vec2(
|
||||
player->body.pos.x + player->body.size.x * 0.5f,
|
||||
player->body.pos.y + player->body.size.y * 0.5f
|
||||
);
|
||||
|
||||
for (int i = 0; i < level->map.exit_zone_count; i++) {
|
||||
const ExitZone *ez = &level->map.exit_zones[i];
|
||||
Vec2 ez_center = vec2(ez->x + ez->w * 0.5f, ez->y + ez->h * 0.5f);
|
||||
float dx = pc.x - ez_center.x;
|
||||
float dy = pc.y - ez_center.y;
|
||||
float dist = sqrtf(dx * dx + dy * dy);
|
||||
|
||||
if (dist < EXIT_SHIP_TRIGGER_DIST) {
|
||||
/* Land the ship so its bottom aligns with the exit zone bottom.
|
||||
* land_pos is the top-left of the ship's resting position. */
|
||||
float land_x = ez->x + ez->w * 0.5f + EXIT_SHIP_LAND_OFFSET_X;
|
||||
float land_y = ez->y + ez->h - (float)SPACECRAFT_HEIGHT;
|
||||
|
||||
Entity *ship = spacecraft_spawn_exit(&level->entities,
|
||||
vec2(land_x, land_y));
|
||||
if (ship) {
|
||||
level->exit_ship_spawned = true;
|
||||
level->exit_zone_idx = i;
|
||||
printf("Exit ship triggered (zone %d, dist=%.0f)\n", i, dist);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle the exit ship boarding and departure sequence. */
|
||||
static void update_exit_ship(Level *level) {
|
||||
if (level->exit_triggered) return;
|
||||
if (!level->exit_ship_spawned) return;
|
||||
|
||||
Entity *ship = find_exit_ship(&level->entities);
|
||||
|
||||
/* Ship may have been destroyed (SC_DONE) */
|
||||
if (!ship) {
|
||||
if (level->exit_ship_boarded) {
|
||||
/* Ship finished its departure — trigger the level exit */
|
||||
const ExitZone *ez = &level->map.exit_zones[level->exit_zone_idx];
|
||||
level->exit_triggered = true;
|
||||
snprintf(level->exit_target, sizeof(level->exit_target),
|
||||
"%s", ez->target);
|
||||
printf("Exit ship departed -> %s\n",
|
||||
ez->target[0] ? ez->target : "(victory)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Wait for the ship to land, then check for player boarding */
|
||||
if (!level->exit_ship_boarded && spacecraft_is_landed(ship)) {
|
||||
Entity *player = find_player(&level->entities);
|
||||
if (player) {
|
||||
/* Check overlap between player and the landed ship */
|
||||
if (physics_overlap(&player->body, &ship->body)) {
|
||||
/* Board the ship: deactivate the player, start takeoff */
|
||||
level->exit_ship_boarded = true;
|
||||
player->active = false;
|
||||
|
||||
spacecraft_takeoff(ship);
|
||||
printf("Player boarded exit ship\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Direct exit zone overlap — fallback for levels without spacecraft exit. */
|
||||
static void check_exit_zones(Level *level) {
|
||||
if (level->exit_triggered) return;
|
||||
if (level->exit_ship_spawned) return; /* ship handles the exit instead */
|
||||
if (level->map.exit_zone_count == 0) return;
|
||||
|
||||
Entity *player = find_player(&level->entities);
|
||||
if (!player) return;
|
||||
|
||||
for (int i = 0; i < level->map.exit_zone_count; i++) {
|
||||
@@ -417,8 +517,9 @@ void level_update(Level *level, float dt) {
|
||||
for (int i = 0; i < level->entities.count; i++) {
|
||||
Entity *e = &level->entities.entities[i];
|
||||
if (e->active && e->type == ENT_SPACECRAFT &&
|
||||
!spacecraft_is_exit_ship(e) &&
|
||||
spacecraft_is_landed(e)) {
|
||||
/* Ship has landed — spawn the player at spawn point */
|
||||
/* Intro ship has landed — spawn the player at spawn point */
|
||||
Entity *player = player_spawn(&level->entities,
|
||||
level->map.player_spawn);
|
||||
if (player) {
|
||||
@@ -443,20 +544,26 @@ void level_update(Level *level, float dt) {
|
||||
if (level->player_spawned) {
|
||||
handle_collisions(&level->entities);
|
||||
|
||||
/* Check exit zones */
|
||||
/* Exit ship: proximity trigger + boarding/departure */
|
||||
check_exit_ship_proximity(level);
|
||||
update_exit_ship(level);
|
||||
|
||||
/* Fallback direct exit zone check (for levels without ship exit) */
|
||||
check_exit_zones(level);
|
||||
|
||||
/* Check for player respawn */
|
||||
for (int i = 0; i < level->entities.count; i++) {
|
||||
Entity *e = &level->entities.entities[i];
|
||||
if (e->active && e->type == ENT_PLAYER && player_wants_respawn(e)) {
|
||||
player_respawn(e, level->map.player_spawn);
|
||||
Vec2 center = vec2(
|
||||
e->body.pos.x + e->body.size.x * 0.5f,
|
||||
e->body.pos.y + e->body.size.y * 0.5f
|
||||
);
|
||||
camera_follow(&level->camera, center, vec2_zero(), dt);
|
||||
break;
|
||||
/* Check for player respawn (skip if player boarded exit ship) */
|
||||
if (!level->exit_ship_boarded) {
|
||||
for (int i = 0; i < level->entities.count; i++) {
|
||||
Entity *e = &level->entities.entities[i];
|
||||
if (e->active && e->type == ENT_PLAYER && player_wants_respawn(e)) {
|
||||
player_respawn(e, level->map.player_spawn);
|
||||
Vec2 center = vec2(
|
||||
e->body.pos.x + e->body.size.x * 0.5f,
|
||||
e->body.pos.y + e->body.size.y * 0.5f
|
||||
);
|
||||
camera_follow(&level->camera, center, vec2_zero(), dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,9 +571,18 @@ void level_update(Level *level, float dt) {
|
||||
/* Update particles */
|
||||
particle_update(dt);
|
||||
|
||||
/* Camera tracking — follow player if spawned, otherwise follow spacecraft */
|
||||
/* Camera tracking:
|
||||
* 1. Exit ship boarded → hold camera still (ship flies out of frame)
|
||||
* 2. Player spawned → follow the player
|
||||
* 3. Intro ship flying in → follow the spacecraft */
|
||||
bool cam_tracked = false;
|
||||
if (level->player_spawned) {
|
||||
|
||||
if (level->exit_ship_boarded) {
|
||||
/* Camera stays put — the ship flies out of frame on its own */
|
||||
cam_tracked = true;
|
||||
}
|
||||
|
||||
if (!cam_tracked && level->player_spawned) {
|
||||
for (int i = 0; i < level->entities.count; i++) {
|
||||
Entity *e = &level->entities.entities[i];
|
||||
if (e->active && e->type == ENT_PLAYER) {
|
||||
@@ -481,11 +597,13 @@ void level_update(Level *level, float dt) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cam_tracked) {
|
||||
/* Follow the spacecraft during intro */
|
||||
for (int i = 0; i < level->entities.count; i++) {
|
||||
Entity *e = &level->entities.entities[i];
|
||||
if (e->active && e->type == ENT_SPACECRAFT) {
|
||||
if (e->active && e->type == ENT_SPACECRAFT &&
|
||||
!spacecraft_is_exit_ship(e)) {
|
||||
Vec2 center = vec2(
|
||||
e->body.pos.x + e->body.size.x * 0.5f,
|
||||
e->body.pos.y + e->body.size.y * 0.5f
|
||||
|
||||
Reference in New Issue
Block a user