Improve levlegen
This commit is contained in:
@@ -127,6 +127,72 @@ static void add_entity(Tilemap *map, const char *type, int tile_x, int tile_y) {
|
|||||||
map->entity_spawn_count++;
|
map->entity_spawn_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════
|
||||||
|
* Segment boundary connectivity helpers
|
||||||
|
*
|
||||||
|
* After all segments are generated, ensure_passage()
|
||||||
|
* scans each column boundary and carves a passable
|
||||||
|
* opening if none exists. This prevents rooms from
|
||||||
|
* being sealed off by adjacent segment tile writes.
|
||||||
|
* ═══════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* Check if a column has a vertical run of at least `need` empty rows
|
||||||
|
* within [row_lo, row_hi]. Returns true if passable. */
|
||||||
|
static bool column_has_gap(const uint16_t *layer, int map_w,
|
||||||
|
int col, int row_lo, int row_hi, int need) {
|
||||||
|
int run = 0;
|
||||||
|
for (int y = row_lo; y <= row_hi; y++) {
|
||||||
|
if (layer[y * map_w + col] == TILE_EMPTY ||
|
||||||
|
layer[y * map_w + col] == TILE_PLAT) {
|
||||||
|
run++;
|
||||||
|
if (run >= need) return true;
|
||||||
|
} else {
|
||||||
|
run = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Carve a 2-column-wide, 4-row-tall opening centered on ground_row.
|
||||||
|
* The opening spans columns [col, col+1] and rows [ground_row-3, ground_row].
|
||||||
|
* This guarantees the player (12x16 px, ~1x2 tiles) can walk through. */
|
||||||
|
static void carve_passage(uint16_t *layer, int map_w, int map_h,
|
||||||
|
int col, int ground_row) {
|
||||||
|
int top = ground_row - 3;
|
||||||
|
if (top < 1) top = 1;
|
||||||
|
int bot = ground_row;
|
||||||
|
if (bot >= map_h) bot = map_h - 1;
|
||||||
|
for (int y = top; y <= bot; y++) {
|
||||||
|
set_tile(layer, map_w, map_h, col, y, TILE_EMPTY);
|
||||||
|
set_tile(layer, map_w, map_h, col + 1, y, TILE_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scan all segment boundaries and ensure connectivity.
|
||||||
|
* seg_x[] holds the starting x of each segment, seg_count entries.
|
||||||
|
* seg_ground[] holds the ground row for each segment. */
|
||||||
|
static void ensure_segment_connectivity(uint16_t *layer, int map_w, int map_h,
|
||||||
|
const int *seg_x, const int *seg_gr,
|
||||||
|
int seg_count) {
|
||||||
|
for (int i = 0; i < seg_count - 1; i++) {
|
||||||
|
int boundary_col = seg_x[i + 1]; /* first column of next segment */
|
||||||
|
int left_col = boundary_col - 1; /* last column of this segment */
|
||||||
|
int right_col = boundary_col;
|
||||||
|
|
||||||
|
/* Use the lower (larger row number) ground of the two segments
|
||||||
|
* so the opening is at floor level for both sides. */
|
||||||
|
int gr = (seg_gr[i] > seg_gr[i + 1]) ? seg_gr[i] : seg_gr[i + 1];
|
||||||
|
|
||||||
|
/* Check both columns at the boundary */
|
||||||
|
bool left_ok = column_has_gap(layer, map_w, left_col, 1, gr, 3);
|
||||||
|
bool right_ok = column_has_gap(layer, map_w, right_col, 1, gr, 3);
|
||||||
|
|
||||||
|
if (!left_ok || !right_ok) {
|
||||||
|
carve_passage(layer, map_w, map_h, left_col, gr - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════
|
||||||
* Segment generators
|
* Segment generators
|
||||||
*
|
*
|
||||||
@@ -323,15 +389,17 @@ static void gen_corridor(Tilemap *map, int x0, int w, int ground_row,
|
|||||||
fill_rect(col, mw, mh, x0, ceil_row, x0, ground_row - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0, ceil_row, x0, ground_row - 1, TILE_SOLID_1);
|
||||||
fill_rect(col, mw, mh, x0 + w - 1, ceil_row, x0 + w - 1, ground_row - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0 + w - 1, ceil_row, x0 + w - 1, ground_row - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
/* Opening in left wall (1 tile above ground to enter) */
|
/* Opening in left wall — 2 tiles wide so adjacent segment can't seal it */
|
||||||
set_tile(col, mw, mh, x0, ground_row - 1, TILE_EMPTY);
|
for (int r = ground_row - 3; r < ground_row; r++) {
|
||||||
set_tile(col, mw, mh, x0, ground_row - 2, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, r, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0, ground_row - 3, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + 1, r, TILE_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
/* Opening in right wall */
|
/* Opening in right wall — 2 tiles wide */
|
||||||
set_tile(col, mw, mh, x0 + w - 1, ground_row - 1, TILE_EMPTY);
|
for (int r = ground_row - 3; r < ground_row; r++) {
|
||||||
set_tile(col, mw, mh, x0 + w - 1, ground_row - 2, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, r, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, ground_row - 3, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 2, r, TILE_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
/* Theme-dependent corridor hazards */
|
/* Theme-dependent corridor hazards */
|
||||||
if (theme == THEME_MARS_BASE) {
|
if (theme == THEME_MARS_BASE) {
|
||||||
@@ -492,14 +560,19 @@ static void gen_shaft(Tilemap *map, int x0, int w, int ground_row,
|
|||||||
fill_rect(col, mw, mh, x0, ceil_row, x0, ground_row - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0, ceil_row, x0, ground_row - 1, TILE_SOLID_1);
|
||||||
fill_rect(col, mw, mh, x0 + w - 1, ceil_row, x0 + w - 1, ground_row - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0 + w - 1, ceil_row, x0 + w - 1, ground_row - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
/* Opening at top */
|
/* Opening at top — 2 tiles wide on each side */
|
||||||
set_tile(col, mw, mh, x0, ceil_row, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, ceil_row, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, ceil_row, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, ceil_row, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, ceil_row, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, ceil_row, TILE_EMPTY);
|
||||||
|
|
||||||
/* Openings at bottom to enter */
|
/* Openings at bottom to enter — 2 tiles wide so adjacent segments
|
||||||
|
* cannot seal them by filling their edge column solid. */
|
||||||
for (int r = ground_row - 3; r < ground_row; r++) {
|
for (int r = ground_row - 3; r < ground_row; r++) {
|
||||||
set_tile(col, mw, mh, x0, r, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, r, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, r, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, r, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, r, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, r, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alternating platforms up the shaft */
|
/* Alternating platforms up the shaft */
|
||||||
@@ -1137,11 +1210,13 @@ bool levelgen_generate(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
/* ── Phase 4: generate segments ── */
|
/* ── Phase 4: generate segments ── */
|
||||||
int cursor = 2; /* start after left buffer */
|
int cursor = 2; /* start after left buffer */
|
||||||
int mh = map->height;
|
int mh = map->height;
|
||||||
|
int seg_start_x[MAX_FINAL_SEGS]; /* track segment x-offsets for connectivity pass */
|
||||||
|
|
||||||
/* Left border wall */
|
/* Left border wall */
|
||||||
fill_rect(map->collision_layer, map->width, mh, 0, 0, 1, mh - 1, TILE_SOLID_1);
|
fill_rect(map->collision_layer, map->width, mh, 0, 0, 1, mh - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
for (int i = 0; i < num_segs; i++) {
|
for (int i = 0; i < num_segs; i++) {
|
||||||
|
seg_start_x[i] = cursor;
|
||||||
int w = seg_widths[i];
|
int w = seg_widths[i];
|
||||||
LevelTheme theme = seg_themes[i];
|
LevelTheme theme = seg_themes[i];
|
||||||
int gr = seg_ground[i];
|
int gr = seg_ground[i];
|
||||||
@@ -1172,6 +1247,10 @@ bool levelgen_generate(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
fill_rect(map->collision_layer, map->width, mh,
|
fill_rect(map->collision_layer, map->width, mh,
|
||||||
map->width - 2, 0, map->width - 1, mh - 1, TILE_SOLID_1);
|
map->width - 2, 0, map->width - 1, mh - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
|
/* ── Phase 4b: ensure no segment boundary is sealed ── */
|
||||||
|
ensure_segment_connectivity(map->collision_layer, map->width, mh,
|
||||||
|
seg_start_x, seg_ground, num_segs);
|
||||||
|
|
||||||
/* ── Phase 5: add visual variety to solid tiles ── */
|
/* ── Phase 5: add visual variety to solid tiles ── */
|
||||||
for (int y = 0; y < map->height; y++) {
|
for (int y = 0; y < map->height; y++) {
|
||||||
for (int x = 0; x < map->width; x++) {
|
for (int x = 0; x < map->width; x++) {
|
||||||
@@ -1333,11 +1412,14 @@ static void gen_station_bulkhead(Tilemap *map, int x0, int w, float difficulty)
|
|||||||
int wall_x = x0 + w / 2;
|
int wall_x = x0 + w / 2;
|
||||||
fill_rect(col, mw, mh, wall_x, STATION_CEIL_ROW + 1, wall_x, STATION_FLOOR_ROW - 1, TILE_SOLID_2);
|
fill_rect(col, mw, mh, wall_x, STATION_CEIL_ROW + 1, wall_x, STATION_FLOOR_ROW - 1, TILE_SOLID_2);
|
||||||
|
|
||||||
/* Doorway opening (3 tiles tall) */
|
/* Doorway opening (4 tiles tall, always reachable from floor).
|
||||||
int door_y = rng_range(STATION_CEIL_ROW + 3, STATION_FLOOR_ROW - 4);
|
* Constrain the door bottom to touch the floor so the player
|
||||||
for (int y = door_y; y < door_y + 3; y++) {
|
* can walk through without needing to jump. */
|
||||||
|
int door_top = STATION_FLOOR_ROW - 4;
|
||||||
|
for (int y = door_top; y < STATION_FLOOR_ROW; y++) {
|
||||||
set_tile(col, mw, mh, wall_x, y, TILE_EMPTY);
|
set_tile(col, mw, mh, wall_x, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
|
int door_y = door_top;
|
||||||
|
|
||||||
/* Turret guarding the doorway — always present */
|
/* Turret guarding the doorway — always present */
|
||||||
add_entity(map, "turret", wall_x - 2, door_y - 1);
|
add_entity(map, "turret", wall_x - 2, door_y - 1);
|
||||||
@@ -1382,13 +1464,21 @@ static void gen_station_platforms(Tilemap *map, int x0, int w, float difficulty)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Floating platforms across the gap */
|
/* Floating platforms across the gap.
|
||||||
|
* First and last platforms are pinned near floor level so the
|
||||||
|
* player can step onto/off the pit section from solid ground. */
|
||||||
int num_plats = rng_range(3, 5);
|
int num_plats = rng_range(3, 5);
|
||||||
int spacing = (pit_end - pit_start) / (num_plats + 1);
|
int spacing = (pit_end - pit_start) / (num_plats + 1);
|
||||||
if (spacing < 2) spacing = 2;
|
if (spacing < 2) spacing = 2;
|
||||||
for (int i = 0; i < num_plats; i++) {
|
for (int i = 0; i < num_plats; i++) {
|
||||||
int px = pit_start + (i + 1) * spacing - 1;
|
int px = pit_start + (i + 1) * spacing - 1;
|
||||||
int py = rng_range(STATION_CEIL_ROW + 4, STATION_FLOOR_ROW - 2);
|
int py;
|
||||||
|
if (i == 0 || i == num_plats - 1) {
|
||||||
|
/* Entry/exit platforms at floor level for walkability */
|
||||||
|
py = STATION_FLOOR_ROW - 1;
|
||||||
|
} else {
|
||||||
|
py = rng_range(STATION_CEIL_ROW + 4, STATION_FLOOR_ROW - 2);
|
||||||
|
}
|
||||||
int pw = rng_range(2, 3);
|
int pw = rng_range(2, 3);
|
||||||
for (int j = 0; j < pw && px + j < x0 + w; j++) {
|
for (int j = 0; j < pw && px + j < x0 + w; j++) {
|
||||||
set_tile(col, mw, mh, px + j, py, TILE_PLAT);
|
set_tile(col, mw, mh, px + j, py, TILE_PLAT);
|
||||||
@@ -1483,14 +1573,23 @@ static void gen_station_vent(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
int vent_ceil = STATION_CEIL_ROW + 3;
|
int vent_ceil = STATION_CEIL_ROW + 3;
|
||||||
fill_rect(col, mw, mh, x0, STATION_CEIL_ROW + 1, x0 + w - 1, vent_ceil, TILE_SOLID_2);
|
fill_rect(col, mw, mh, x0, STATION_CEIL_ROW + 1, x0 + w - 1, vent_ceil, TILE_SOLID_2);
|
||||||
|
|
||||||
/* Opening at left */
|
/* Opening at left — both at vent level and at floor level so the
|
||||||
|
* player can enter from the standard corridor floor. */
|
||||||
for (int y = vent_ceil - 1; y <= vent_ceil + 2 && y < STATION_FLOOR_ROW; y++) {
|
for (int y = vent_ceil - 1; y <= vent_ceil + 2 && y < STATION_FLOOR_ROW; y++) {
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
/* Opening at right */
|
for (int y = STATION_FLOOR_ROW - 3; y < STATION_FLOOR_ROW; y++) {
|
||||||
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
|
}
|
||||||
|
/* Opening at right — same treatment */
|
||||||
for (int y = vent_ceil - 1; y <= vent_ceil + 2 && y < STATION_FLOOR_ROW; y++) {
|
for (int y = vent_ceil - 1; y <= vent_ceil + 2 && y < STATION_FLOOR_ROW; y++) {
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
|
for (int y = STATION_FLOOR_ROW - 3; y < STATION_FLOOR_ROW; y++) {
|
||||||
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
/* Flame vents along the floor — always present, more at higher difficulty */
|
/* Flame vents along the floor — always present, more at higher difficulty */
|
||||||
int num_vents = 1 + (int)(difficulty * 2);
|
int num_vents = 1 + (int)(difficulty * 2);
|
||||||
@@ -1641,6 +1740,8 @@ bool levelgen_generate_station(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
/* ── Phase 4: generate segments ── */
|
/* ── Phase 4: generate segments ── */
|
||||||
int cursor = 2;
|
int cursor = 2;
|
||||||
int smh = map->height;
|
int smh = map->height;
|
||||||
|
int sseg_start_x[20];
|
||||||
|
int sseg_ground[20];
|
||||||
|
|
||||||
/* Left border wall */
|
/* Left border wall */
|
||||||
fill_rect(map->collision_layer, map->width, smh, 0, 0, 1, smh - 1, TILE_SOLID_1);
|
fill_rect(map->collision_layer, map->width, smh, 0, 0, 1, smh - 1, TILE_SOLID_1);
|
||||||
@@ -1650,6 +1751,8 @@ bool levelgen_generate_station(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < num_segs; i++) {
|
for (int i = 0; i < num_segs; i++) {
|
||||||
|
sseg_start_x[i] = cursor;
|
||||||
|
sseg_ground[i] = STATION_FLOOR_ROW;
|
||||||
int w = seg_widths[i];
|
int w = seg_widths[i];
|
||||||
float diff = config->difficulty;
|
float diff = config->difficulty;
|
||||||
|
|
||||||
@@ -1670,6 +1773,10 @@ bool levelgen_generate_station(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
fill_rect(map->collision_layer, map->width, smh,
|
fill_rect(map->collision_layer, map->width, smh,
|
||||||
map->width - 2, 0, map->width - 1, smh - 1, TILE_SOLID_1);
|
map->width - 2, 0, map->width - 1, smh - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
|
/* ── Phase 4b: ensure no segment boundary is sealed ── */
|
||||||
|
ensure_segment_connectivity(map->collision_layer, map->width, smh,
|
||||||
|
sseg_start_x, sseg_ground, num_segs);
|
||||||
|
|
||||||
/* ── Phase 5: visual variety ── */
|
/* ── Phase 5: visual variety ── */
|
||||||
for (int y = 0; y < map->height; y++) {
|
for (int y = 0; y < map->height; y++) {
|
||||||
for (int x = 0; x < map->width; x++) {
|
for (int x = 0; x < map->width; x++) {
|
||||||
@@ -1788,9 +1895,15 @@ static void gen_mb_entry(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
fill_rect(col, mw, mh, x0, MB_CEIL_ROW + 1, x0, MB_MID_UPPER - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0, MB_CEIL_ROW + 1, x0, MB_MID_UPPER - 1, TILE_SOLID_1);
|
||||||
fill_rect(col, mw, mh, x0 + w - 1, MB_CEIL_ROW + 1, x0 + w - 1, MB_MID_UPPER - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0 + w - 1, MB_CEIL_ROW + 1, x0 + w - 1, MB_MID_UPPER - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
/* Opening on the right wall to exit to the next segment */
|
/* Openings on the right wall at all standard heights so the
|
||||||
for (int y = MB_MID_UPPER - 4; y < MB_MID_UPPER; y++) {
|
* next segment is reachable regardless of its layout. */
|
||||||
|
int entry_open_rows[] = { MB_MID_UPPER, MB_MID_LOWER, MB_FLOOR_ROW };
|
||||||
|
for (int h = 0; h < 3; h++) {
|
||||||
|
int base = entry_open_rows[h];
|
||||||
|
for (int y = base - 4; y < base; y++) {
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Health pickup */
|
/* Health pickup */
|
||||||
@@ -1814,14 +1927,16 @@ static void gen_mb_shaft(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
fill_rect(col, mw, mh, x0, MB_CEIL_ROW + 1, x0, MB_FLOOR_ROW - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0, MB_CEIL_ROW + 1, x0, MB_FLOOR_ROW - 1, TILE_SOLID_1);
|
||||||
fill_rect(col, mw, mh, x0 + w - 1, MB_CEIL_ROW + 1, x0 + w - 1, MB_FLOOR_ROW - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0 + w - 1, MB_CEIL_ROW + 1, x0 + w - 1, MB_FLOOR_ROW - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
/* Openings at top and bottom of walls for connectivity */
|
/* Openings at all standard heights — 2 tiles wide */
|
||||||
for (int y = MB_CEIL_ROW + 1; y < MB_CEIL_ROW + 5; y++) {
|
int shaft_open_rows[] = { MB_CEIL_ROW + 4, MB_MID_UPPER, MB_MID_LOWER, MB_FLOOR_ROW };
|
||||||
|
for (int h = 0; h < 4; h++) {
|
||||||
|
int base = shaft_open_rows[h];
|
||||||
|
for (int y = base - 4; y < base; y++) {
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
for (int y = MB_FLOOR_ROW - 4; y < MB_FLOOR_ROW; y++) {
|
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alternating platforms climbing up the shaft */
|
/* Alternating platforms climbing up the shaft */
|
||||||
@@ -1916,10 +2031,25 @@ static void gen_mb_corridor(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
/* Side walls with openings */
|
/* Side walls with openings */
|
||||||
fill_rect(col, mw, mh, x0, ceil_row + 2, x0, floor_row - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0, ceil_row + 2, x0, floor_row - 1, TILE_SOLID_1);
|
||||||
fill_rect(col, mw, mh, x0 + w - 1, ceil_row + 2, x0 + w - 1, floor_row - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0 + w - 1, ceil_row + 2, x0 + w - 1, floor_row - 1, TILE_SOLID_1);
|
||||||
/* Door openings */
|
/* Door openings at this corridor's level — 2 tiles wide */
|
||||||
for (int y = floor_row - 4; y < floor_row; y++) {
|
for (int y = floor_row - 4; y < floor_row; y++) {
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
|
}
|
||||||
|
/* Also open at standard heights so adjacent segments at different
|
||||||
|
* levels can still be reached. Vertical shafts or platforms inside
|
||||||
|
* the adjacent segment handle the height transition. */
|
||||||
|
int mb_open_rows[] = { MB_MID_UPPER, MB_MID_LOWER, MB_FLOOR_ROW };
|
||||||
|
for (int h = 0; h < 3; h++) {
|
||||||
|
int base = mb_open_rows[h];
|
||||||
|
for (int y = base - 4; y < base; y++) {
|
||||||
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Charger patrol */
|
/* Charger patrol */
|
||||||
@@ -1971,18 +2101,16 @@ static void gen_mb_turret_hall(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
set_tile(col, mw, mh, hole2_x + j, MB_MID_LOWER + 1, TILE_EMPTY);
|
set_tile(col, mw, mh, hole2_x + j, MB_MID_LOWER + 1, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Door openings on sides at each level */
|
/* Door openings on sides at all standard heights — 2 tiles wide */
|
||||||
for (int y = MB_MID_UPPER - 4; y < MB_MID_UPPER; y++) {
|
int th_open_rows[] = { MB_MID_UPPER, MB_MID_LOWER, MB_FLOOR_ROW };
|
||||||
|
for (int h = 0; h < 3; h++) {
|
||||||
|
int base = th_open_rows[h];
|
||||||
|
for (int y = base - 4; y < base; y++) {
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
for (int y = MB_MID_LOWER - 4; y < MB_MID_LOWER; y++) {
|
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
|
||||||
}
|
|
||||||
for (int y = MB_FLOOR_ROW - 4; y < MB_FLOOR_ROW; y++) {
|
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Laser turrets on walls at each level — the gauntlet */
|
/* Laser turrets on walls at each level — the gauntlet */
|
||||||
@@ -2040,14 +2168,16 @@ static void gen_mb_hive(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
set_tile(col, mw, mh, hole_x + j, MB_MID_LOWER + 1, TILE_EMPTY);
|
set_tile(col, mw, mh, hole_x + j, MB_MID_LOWER + 1, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Door openings on sides */
|
/* Door openings at all standard heights — 2 tiles wide */
|
||||||
for (int y = MB_MID_LOWER - 4; y < MB_MID_LOWER; y++) {
|
int hive_open_rows[] = { MB_MID_UPPER, MB_MID_LOWER, MB_FLOOR_ROW };
|
||||||
|
for (int h = 0; h < 3; h++) {
|
||||||
|
int base = hive_open_rows[h];
|
||||||
|
for (int y = base - 4; y < base; y++) {
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
for (int y = MB_FLOOR_ROW - 4; y < MB_FLOOR_ROW; y++) {
|
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Spawner in the upper area — the hive */
|
/* Spawner in the upper area — the hive */
|
||||||
@@ -2114,14 +2244,16 @@ static void gen_mb_arena(Tilemap *map, int x0, int w, float difficulty) {
|
|||||||
/* Ground floor for walking */
|
/* Ground floor for walking */
|
||||||
fill_rect(col, mw, mh, x0 + 1, MB_FLOOR_ROW - 1, x0 + w - 2, MB_FLOOR_ROW - 1, TILE_SOLID_1);
|
fill_rect(col, mw, mh, x0 + 1, MB_FLOOR_ROW - 1, x0 + w - 2, MB_FLOOR_ROW - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
/* Door openings on sides */
|
/* Door openings at all standard heights — 2 tiles wide */
|
||||||
for (int y = MB_FLOOR_ROW - 5; y < MB_FLOOR_ROW - 1; y++) {
|
int arena_open_rows[] = { MB_CEIL_ROW + 4, MB_MID_UPPER, MB_MID_LOWER, MB_FLOOR_ROW };
|
||||||
|
for (int h = 0; h < 4; h++) {
|
||||||
|
int base = arena_open_rows[h];
|
||||||
|
for (int y = base - 4; y < base; y++) {
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + 1, y, TILE_EMPTY);
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
||||||
|
set_tile(col, mw, mh, x0 + w - 2, y, TILE_EMPTY);
|
||||||
}
|
}
|
||||||
for (int y = MB_CEIL_ROW + 1; y < MB_CEIL_ROW + 5; y++) {
|
|
||||||
set_tile(col, mw, mh, x0, y, TILE_EMPTY);
|
|
||||||
set_tile(col, mw, mh, x0 + w - 1, y, TILE_EMPTY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enemies — mixed chargers, grunts, flyers across levels */
|
/* Enemies — mixed chargers, grunts, flyers across levels */
|
||||||
@@ -2262,6 +2394,8 @@ bool levelgen_generate_mars_base(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
|
|
||||||
/* ── Phase 4: generate segments ── */
|
/* ── Phase 4: generate segments ── */
|
||||||
int cursor = 2;
|
int cursor = 2;
|
||||||
|
int mb_seg_start_x[12];
|
||||||
|
int mb_seg_ground[12];
|
||||||
|
|
||||||
/* Left border wall */
|
/* Left border wall */
|
||||||
fill_rect(map->collision_layer, map->width, map->height,
|
fill_rect(map->collision_layer, map->width, map->height,
|
||||||
@@ -2272,6 +2406,8 @@ bool levelgen_generate_mars_base(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < num_segs; i++) {
|
for (int i = 0; i < num_segs; i++) {
|
||||||
|
mb_seg_start_x[i] = cursor;
|
||||||
|
mb_seg_ground[i] = MB_FLOOR_ROW;
|
||||||
int w = seg_widths[i];
|
int w = seg_widths[i];
|
||||||
float diff = config->difficulty;
|
float diff = config->difficulty;
|
||||||
|
|
||||||
@@ -2292,6 +2428,10 @@ bool levelgen_generate_mars_base(Tilemap *map, const LevelGenConfig *config) {
|
|||||||
fill_rect(map->collision_layer, map->width, map->height,
|
fill_rect(map->collision_layer, map->width, map->height,
|
||||||
map->width - 2, 0, map->width - 1, map->height - 1, TILE_SOLID_1);
|
map->width - 2, 0, map->width - 1, map->height - 1, TILE_SOLID_1);
|
||||||
|
|
||||||
|
/* ── Phase 4b: ensure no segment boundary is sealed ── */
|
||||||
|
ensure_segment_connectivity(map->collision_layer, map->width, map->height,
|
||||||
|
mb_seg_start_x, mb_seg_ground, num_segs);
|
||||||
|
|
||||||
/* ── Phase 5: visual variety (random solid variants for interior tiles) ── */
|
/* ── Phase 5: visual variety (random solid variants for interior tiles) ── */
|
||||||
for (int y = 0; y < map->height; y++) {
|
for (int y = 0; y < map->height; y++) {
|
||||||
for (int x = 0; x < map->width; x++) {
|
for (int x = 0; x < map->width; x++) {
|
||||||
|
|||||||
Reference in New Issue
Block a user