Support large maps up to 8192x8192 tiles
Raise hard map size cap from 4096 to 8192 via MAX_MAP_SIZE constant. Replace fixed 16 KB fgets buffer with dynamic line reader that handles arbitrarily long tile rows. Check calloc returns for tile layer allocation. Add entity distance culling: skip updates for entities beyond 2x screen width from camera, skip rendering beyond 64 px margin. Dead entities bypass culling so cleanup callbacks always run. Player, spacecraft, drone, and moving platforms set ENTITY_ALWAYS_UPDATE to opt out. Replace naive 4-neighbor flood fill with scanline algorithm (O(height) stack instead of O(area)) for safe use on large maps. Raise MAX_ENTITY_SPAWNS from 128 to 512 and MAX_EXIT_ZONES from 8 to 16 to support populated large levels.
This commit is contained in:
24
TODO.md
24
TODO.md
@@ -11,15 +11,21 @@ with state machine (FLYING_IN → LANDING → LANDED → TAKEOFF → FLYING_OUT
|
|||||||
DONE), engine/synth sounds, thruster particles, level intro sequence with
|
DONE), engine/synth sounds, thruster particles, level intro sequence with
|
||||||
deferred player spawn.
|
deferred player spawn.
|
||||||
|
|
||||||
## Large map support (5000x5000)
|
## ~~Large map support (5000x5000)~~ ✓
|
||||||
Audit the engine for anything that breaks or becomes slow at very large map
|
Audited and fixed all engine bottlenecks for maps up to 8192x8192:
|
||||||
sizes. Key areas to check:
|
- `MAX_MAP_SIZE` constant (8192) replaces hard-coded 4096 caps in tilemap
|
||||||
- Tile layer allocation (`uint16_t *` for 25M tiles)
|
loader and editor resize. Tile layers allocate fine at 5000x5000 (~143 MB).
|
||||||
- Tilemap rendering culling (already viewport-clipped, verify correctness)
|
- Dynamic line reader in `tilemap_load()` replaces fixed 16 KB `fgets` buffer;
|
||||||
- Physics / collision queries (should only check nearby tiles)
|
handles arbitrarily long tile rows without truncation.
|
||||||
- Entity updates (currently iterates full pool regardless of distance)
|
- `MAX_ENTITY_SPAWNS` raised from 128 to 512; `MAX_EXIT_ZONES` from 8 to 16.
|
||||||
- Camera bounds and coordinate overflow (float precision at large coords)
|
- Entity distance culling: `entity_update_all()` skips entities beyond 2×
|
||||||
- Level file parsing (row lines could exceed fgets buffer at 5000+ columns)
|
screen width from the camera; `entity_render_all()` skips entities outside
|
||||||
|
the viewport + 64 px margin. Player, spacecraft, and drone use
|
||||||
|
`ENTITY_ALWAYS_UPDATE` flag to opt out of culling.
|
||||||
|
- Editor flood fill replaced with scanline algorithm — O(height) stack usage
|
||||||
|
instead of O(area), safe for very large maps.
|
||||||
|
- Verified: tilemap rendering already viewport-culled, physics queries are O(1)
|
||||||
|
local tile lookups, float precision fine up to ~1M pixels (62 K tiles).
|
||||||
|
|
||||||
## ~~Spacecraft at level exit~~ ✓
|
## ~~Spacecraft at level exit~~ ✓
|
||||||
Implemented: `spacecraft_spawn_exit()` with `is_exit_ship` flag. Proximity
|
Implemented: `spacecraft_spawn_exit()` with `is_exit_ship` flag. Proximity
|
||||||
|
|||||||
@@ -19,13 +19,14 @@
|
|||||||
/* ── Tiles ──────────────────────────────────────────── */
|
/* ── Tiles ──────────────────────────────────────────── */
|
||||||
#define TILE_SIZE 16 /* pixels per tile */
|
#define TILE_SIZE 16 /* pixels per tile */
|
||||||
#define MAX_TILE_DEFS 256 /* unique tile types */
|
#define MAX_TILE_DEFS 256 /* unique tile types */
|
||||||
|
#define MAX_MAP_SIZE 8192 /* max width or height in tiles */
|
||||||
|
|
||||||
/* ── Entities ───────────────────────────────────────── */
|
/* ── Entities ───────────────────────────────────────── */
|
||||||
#define MAX_ENTITIES 512
|
#define MAX_ENTITIES 512
|
||||||
#define MAX_ENTITY_SPAWNS 128 /* max entity spawns per level */
|
#define MAX_ENTITY_SPAWNS 512 /* max entity spawns per level */
|
||||||
|
|
||||||
/* ── Level transitions ─────────────────────────────── */
|
/* ── Level transitions ─────────────────────────────── */
|
||||||
#define MAX_EXIT_ZONES 8 /* max exit zones per level */
|
#define MAX_EXIT_ZONES 16 /* max exit zones per level */
|
||||||
|
|
||||||
/* ── Rendering ──────────────────────────────────────── */
|
/* ── Rendering ──────────────────────────────────────── */
|
||||||
#define MAX_SPRITES 2048 /* max queued sprites per frame */
|
#define MAX_SPRITES 2048 /* max queued sprites per frame */
|
||||||
|
|||||||
@@ -1,7 +1,33 @@
|
|||||||
#include "engine/entity.h"
|
#include "engine/entity.h"
|
||||||
|
#include "engine/camera.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* ── Distance culling ──────────────────────────────── */
|
||||||
|
|
||||||
|
/* Margin around the viewport for update culling (in pixels).
|
||||||
|
* Entities within this margin still update even if off-screen,
|
||||||
|
* so they are active before scrolling into view. */
|
||||||
|
#define UPDATE_MARGIN (SCREEN_WIDTH * 2.0f)
|
||||||
|
|
||||||
|
/* Margin for render culling — tighter than update since only
|
||||||
|
* entities actually visible (plus a small buffer) need drawing. */
|
||||||
|
#define RENDER_MARGIN 64.0f
|
||||||
|
|
||||||
|
/* Check whether a body is within a rectangle expanded by margin. */
|
||||||
|
static bool in_camera_range(const Body *body, const Camera *cam, float margin) {
|
||||||
|
float left = cam->pos.x - margin;
|
||||||
|
float top = cam->pos.y - margin;
|
||||||
|
float right = cam->pos.x + cam->viewport.x + margin;
|
||||||
|
float bottom = cam->pos.y + cam->viewport.y + margin;
|
||||||
|
|
||||||
|
float ex = body->pos.x + body->size.x;
|
||||||
|
float ey = body->pos.y + body->size.y;
|
||||||
|
|
||||||
|
return body->pos.x < right && ex > left &&
|
||||||
|
body->pos.y < bottom && ey > top;
|
||||||
|
}
|
||||||
|
|
||||||
void entity_manager_init(EntityManager *em) {
|
void entity_manager_init(EntityManager *em) {
|
||||||
memset(em, 0, sizeof(EntityManager));
|
memset(em, 0, sizeof(EntityManager));
|
||||||
}
|
}
|
||||||
@@ -43,7 +69,8 @@ void entity_destroy(EntityManager *em, Entity *e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void entity_update_all(EntityManager *em, float dt, const Tilemap *map) {
|
void entity_update_all(EntityManager *em, float dt, const Tilemap *map,
|
||||||
|
const Camera *cam) {
|
||||||
for (int i = 0; i < em->count; i++) {
|
for (int i = 0; i < em->count; i++) {
|
||||||
Entity *e = &em->entities[i];
|
Entity *e = &em->entities[i];
|
||||||
if (!e->active) continue;
|
if (!e->active) continue;
|
||||||
@@ -53,6 +80,14 @@ void entity_update_all(EntityManager *em, float dt, const Tilemap *map) {
|
|||||||
e->flags |= ENTITY_DEAD;
|
e->flags |= ENTITY_DEAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Distance culling: skip far-away entities unless they opt out.
|
||||||
|
* Dead entities always get their update callback so type-specific
|
||||||
|
* cleanup (destroy, particles, etc.) can run. */
|
||||||
|
if (cam && !(e->flags & (ENTITY_ALWAYS_UPDATE | ENTITY_DEAD))) {
|
||||||
|
if (!in_camera_range(&e->body, cam, UPDATE_MARGIN))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Call type-specific update */
|
/* Call type-specific update */
|
||||||
if (em->update_fn[e->type]) {
|
if (em->update_fn[e->type]) {
|
||||||
em->update_fn[e->type](e, dt, map);
|
em->update_fn[e->type](e, dt, map);
|
||||||
@@ -65,6 +100,10 @@ void entity_render_all(EntityManager *em, const Camera *cam) {
|
|||||||
Entity *e = &em->entities[i];
|
Entity *e = &em->entities[i];
|
||||||
if (!e->active) continue;
|
if (!e->active) continue;
|
||||||
|
|
||||||
|
/* Render culling: skip entities outside the visible area. */
|
||||||
|
if (cam && !in_camera_range(&e->body, cam, RENDER_MARGIN))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (em->render_fn[e->type]) {
|
if (em->render_fn[e->type]) {
|
||||||
em->render_fn[e->type](e, cam);
|
em->render_fn[e->type](e, cam);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ typedef enum EntityType {
|
|||||||
} EntityType;
|
} EntityType;
|
||||||
|
|
||||||
/* Entity flags */
|
/* Entity flags */
|
||||||
#define ENTITY_INVINCIBLE (1 << 0)
|
#define ENTITY_INVINCIBLE (1 << 0)
|
||||||
#define ENTITY_FACING_LEFT (1 << 1)
|
#define ENTITY_FACING_LEFT (1 << 1)
|
||||||
#define ENTITY_DEAD (1 << 2)
|
#define ENTITY_DEAD (1 << 2)
|
||||||
|
#define ENTITY_ALWAYS_UPDATE (1 << 3) /* never culled by distance */
|
||||||
|
|
||||||
typedef struct Entity {
|
typedef struct Entity {
|
||||||
EntityType type;
|
EntityType type;
|
||||||
@@ -66,7 +67,8 @@ typedef struct EntityManager {
|
|||||||
void entity_manager_init(EntityManager *em);
|
void entity_manager_init(EntityManager *em);
|
||||||
Entity *entity_spawn(EntityManager *em, EntityType type, Vec2 pos);
|
Entity *entity_spawn(EntityManager *em, EntityType type, Vec2 pos);
|
||||||
void entity_destroy(EntityManager *em, Entity *e);
|
void entity_destroy(EntityManager *em, Entity *e);
|
||||||
void entity_update_all(EntityManager *em, float dt, const Tilemap *map);
|
void entity_update_all(EntityManager *em, float dt, const Tilemap *map,
|
||||||
|
const Camera *cam);
|
||||||
void entity_render_all(EntityManager *em, const Camera *cam);
|
void entity_render_all(EntityManager *em, const Camera *cam);
|
||||||
void entity_manager_clear(EntityManager *em);
|
void entity_manager_clear(EntityManager *em);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,33 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Read a full line from f into a dynamically growing buffer.
|
||||||
|
* *buf and *cap track the heap buffer; the caller must free *buf.
|
||||||
|
* Returns the line length, or -1 on EOF/error. */
|
||||||
|
static int read_line(FILE *f, char **buf, int *cap) {
|
||||||
|
if (!*buf) {
|
||||||
|
*cap = 16384;
|
||||||
|
*buf = malloc(*cap);
|
||||||
|
if (!*buf) return -1;
|
||||||
|
}
|
||||||
|
int len = 0;
|
||||||
|
for (;;) {
|
||||||
|
if (len + 1 >= *cap) {
|
||||||
|
int new_cap = *cap * 2;
|
||||||
|
char *tmp = realloc(*buf, new_cap);
|
||||||
|
if (!tmp) return -1;
|
||||||
|
*buf = tmp;
|
||||||
|
*cap = new_cap;
|
||||||
|
}
|
||||||
|
int ch = fgetc(f);
|
||||||
|
if (ch == EOF) return len > 0 ? len : -1;
|
||||||
|
(*buf)[len++] = (char)ch;
|
||||||
|
if (ch == '\n') break;
|
||||||
|
}
|
||||||
|
(*buf)[len] = '\0';
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
||||||
FILE *f = fopen(path, "r");
|
FILE *f = fopen(path, "r");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
@@ -15,12 +42,13 @@ bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
|||||||
|
|
||||||
memset(map, 0, sizeof(Tilemap));
|
memset(map, 0, sizeof(Tilemap));
|
||||||
|
|
||||||
char line[16384];
|
char *line = NULL;
|
||||||
char tileset_path[256] = {0};
|
int line_cap = 0;
|
||||||
int current_layer = -1; /* 0=collision, 1=bg, 2=fg */
|
char tileset_path[256] = {0};
|
||||||
int row = 0;
|
int current_layer = -1; /* 0=collision, 1=bg, 2=fg */
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), f)) {
|
while (read_line(f, &line, &line_cap) >= 0) {
|
||||||
/* Skip comments and empty lines */
|
/* Skip comments and empty lines */
|
||||||
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
|
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;
|
||||||
|
|
||||||
@@ -30,8 +58,9 @@ bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
|||||||
} else if (strncmp(line, "SIZE ", 5) == 0) {
|
} else if (strncmp(line, "SIZE ", 5) == 0) {
|
||||||
sscanf(line + 5, "%d %d", &map->width, &map->height);
|
sscanf(line + 5, "%d %d", &map->width, &map->height);
|
||||||
if (map->width <= 0 || map->height <= 0 ||
|
if (map->width <= 0 || map->height <= 0 ||
|
||||||
map->width > 4096 || map->height > 4096) {
|
map->width > MAX_MAP_SIZE || map->height > MAX_MAP_SIZE) {
|
||||||
fprintf(stderr, "Invalid map size: %dx%d\n", map->width, map->height);
|
fprintf(stderr, "Invalid map size: %dx%d\n", map->width, map->height);
|
||||||
|
free(line);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -39,6 +68,17 @@ bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
|||||||
map->collision_layer = calloc(total, sizeof(uint16_t));
|
map->collision_layer = calloc(total, sizeof(uint16_t));
|
||||||
map->bg_layer = calloc(total, sizeof(uint16_t));
|
map->bg_layer = calloc(total, sizeof(uint16_t));
|
||||||
map->fg_layer = calloc(total, sizeof(uint16_t));
|
map->fg_layer = calloc(total, sizeof(uint16_t));
|
||||||
|
if (!map->collision_layer || !map->bg_layer || !map->fg_layer) {
|
||||||
|
fprintf(stderr, "Failed to allocate tile layers (%dx%d)\n",
|
||||||
|
map->width, map->height);
|
||||||
|
free(map->collision_layer);
|
||||||
|
free(map->bg_layer);
|
||||||
|
free(map->fg_layer);
|
||||||
|
memset(map, 0, sizeof(Tilemap));
|
||||||
|
free(line);
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (strncmp(line, "SPAWN ", 6) == 0) {
|
} else if (strncmp(line, "SPAWN ", 6) == 0) {
|
||||||
float sx, sy;
|
float sx, sy;
|
||||||
sscanf(line + 6, "%f %f", &sx, &sy);
|
sscanf(line + 6, "%f %f", &sx, &sy);
|
||||||
@@ -132,6 +172,7 @@ bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
free(line);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
/* Load tileset texture */
|
/* Load tileset texture */
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ Entity *drone_spawn(EntityManager *em, Vec2 player_pos) {
|
|||||||
e->health = 99; /* practically invulnerable */
|
e->health = 99; /* practically invulnerable */
|
||||||
e->max_health = 99;
|
e->max_health = 99;
|
||||||
e->damage = 0;
|
e->damage = 0;
|
||||||
e->flags |= ENTITY_INVINCIBLE;
|
e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;
|
||||||
|
|
||||||
DroneData *dd = calloc(1, sizeof(DroneData));
|
DroneData *dd = calloc(1, sizeof(DroneData));
|
||||||
dd->orbit_angle = 0.0f;
|
dd->orbit_angle = 0.0f;
|
||||||
|
|||||||
@@ -279,36 +279,82 @@ static void set_tile(Tilemap *map, uint16_t *layer, int tx, int ty, uint16_t id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════
|
||||||
* Flood fill (iterative, stack-based)
|
* Flood fill (scanline algorithm)
|
||||||
|
*
|
||||||
|
* Processes entire horizontal spans at once, then
|
||||||
|
* pushes only the boundary seeds above and below.
|
||||||
|
* Much more memory-efficient than naive 4-neighbor
|
||||||
|
* fill on large maps (O(height) stack vs O(area)).
|
||||||
* ═══════════════════════════════════════════════════ */
|
* ═══════════════════════════════════════════════════ */
|
||||||
|
|
||||||
typedef struct FillNode { int x, y; } FillNode;
|
typedef struct FillSpan { int x_left, x_right, y, dir; } FillSpan;
|
||||||
|
|
||||||
static void flood_fill(Tilemap *map, uint16_t *layer, int sx, int sy, uint16_t new_id) {
|
static void flood_fill(Tilemap *map, uint16_t *layer, int sx, int sy, uint16_t new_id) {
|
||||||
uint16_t old_id = get_tile(map, layer, sx, sy);
|
uint16_t old_id = get_tile(map, layer, sx, sy);
|
||||||
if (old_id == new_id) return;
|
if (old_id == new_id) return;
|
||||||
|
|
||||||
int capacity = 1024;
|
int capacity = 1024;
|
||||||
FillNode *stack = malloc(capacity * sizeof(FillNode));
|
FillSpan *stack = malloc(capacity * sizeof(FillSpan));
|
||||||
|
if (!stack) return;
|
||||||
int top = 0;
|
int top = 0;
|
||||||
stack[top++] = (FillNode){sx, sy};
|
|
||||||
|
/* Fill the initial span containing (sx, sy). */
|
||||||
|
int xl = sx, xr = sx;
|
||||||
|
while (xl > 0 && get_tile(map, layer, xl - 1, sy) == old_id) xl--;
|
||||||
|
while (xr < map->width - 1 && get_tile(map, layer, xr + 1, sy) == old_id) xr++;
|
||||||
|
for (int x = xl; x <= xr; x++) set_tile(map, layer, x, sy, new_id);
|
||||||
|
|
||||||
|
/* Seed rows above and below. */
|
||||||
|
stack[top++] = (FillSpan){xl, xr, sy, -1};
|
||||||
|
stack[top++] = (FillSpan){xl, xr, sy, 1};
|
||||||
|
|
||||||
while (top > 0) {
|
while (top > 0) {
|
||||||
FillNode n = stack[--top];
|
FillSpan s = stack[--top];
|
||||||
if (n.x < 0 || n.x >= map->width || n.y < 0 || n.y >= map->height) continue;
|
int ny = s.y + s.dir;
|
||||||
if (get_tile(map, layer, n.x, n.y) != old_id) continue;
|
if (ny < 0 || ny >= map->height) continue;
|
||||||
|
|
||||||
set_tile(map, layer, n.x, n.y, new_id);
|
/* Scan the row for sub-spans that match old_id and are seeded
|
||||||
|
* by the parent span [x_left, x_right]. */
|
||||||
|
int x = s.x_left;
|
||||||
|
while (x <= s.x_right) {
|
||||||
|
/* Skip non-matching tiles. */
|
||||||
|
if (get_tile(map, layer, x, ny) != old_id) { x++; continue; }
|
||||||
|
|
||||||
/* Grow stack if needed */
|
/* Found a matching run; expand it fully. */
|
||||||
if (top + 4 >= capacity) {
|
int run_l = x;
|
||||||
capacity *= 2;
|
while (run_l > 0 && get_tile(map, layer, run_l - 1, ny) == old_id) run_l--;
|
||||||
stack = realloc(stack, capacity * sizeof(FillNode));
|
int run_r = x;
|
||||||
|
while (run_r < map->width - 1 && get_tile(map, layer, run_r + 1, ny) == old_id) run_r++;
|
||||||
|
|
||||||
|
/* Fill this run. */
|
||||||
|
for (int fx = run_l; fx <= run_r; fx++) set_tile(map, layer, fx, ny, new_id);
|
||||||
|
|
||||||
|
/* Grow stack if needed. */
|
||||||
|
if (top + 2 >= capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
FillSpan *tmp = realloc(stack, capacity * sizeof(FillSpan));
|
||||||
|
if (!tmp) { fprintf(stderr, "Warning: flood fill out of memory\n"); free(stack); return; }
|
||||||
|
stack = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue in the same direction. */
|
||||||
|
stack[top++] = (FillSpan){run_l, run_r, ny, s.dir};
|
||||||
|
/* Also seed the opposite direction for portions that extend
|
||||||
|
* beyond the parent span (leak-around fills). */
|
||||||
|
if (run_l < s.x_left)
|
||||||
|
stack[top++] = (FillSpan){run_l, s.x_left - 1, ny, -s.dir};
|
||||||
|
if (run_r > s.x_right) {
|
||||||
|
if (top + 1 >= capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
FillSpan *tmp = realloc(stack, capacity * sizeof(FillSpan));
|
||||||
|
if (!tmp) { fprintf(stderr, "Warning: flood fill out of memory\n"); free(stack); return; }
|
||||||
|
stack = tmp;
|
||||||
|
}
|
||||||
|
stack[top++] = (FillSpan){s.x_right + 1, run_r, ny, -s.dir};
|
||||||
|
}
|
||||||
|
|
||||||
|
x = run_r + 1;
|
||||||
}
|
}
|
||||||
stack[top++] = (FillNode){n.x + 1, n.y};
|
|
||||||
stack[top++] = (FillNode){n.x - 1, n.y};
|
|
||||||
stack[top++] = (FillNode){n.x, n.y + 1};
|
|
||||||
stack[top++] = (FillNode){n.x, n.y - 1};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(stack);
|
free(stack);
|
||||||
@@ -430,8 +476,8 @@ static void resize_layer(uint16_t **layer, int old_w, int old_h, int new_w, int
|
|||||||
static void editor_resize(Editor *ed, int new_w, int new_h) {
|
static void editor_resize(Editor *ed, int new_w, int new_h) {
|
||||||
if (new_w < 10) new_w = 10;
|
if (new_w < 10) new_w = 10;
|
||||||
if (new_h < 10) new_h = 10;
|
if (new_h < 10) new_h = 10;
|
||||||
if (new_w > 4096) new_w = 4096;
|
if (new_w > MAX_MAP_SIZE) new_w = MAX_MAP_SIZE;
|
||||||
if (new_h > 4096) new_h = 4096;
|
if (new_h > MAX_MAP_SIZE) new_h = MAX_MAP_SIZE;
|
||||||
|
|
||||||
int old_w = ed->map.width;
|
int old_w = ed->map.width;
|
||||||
int old_h = ed->map.height;
|
int old_h = ed->map.height;
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ Entity *mplat_spawn_dir(EntityManager *em, Vec2 pos, Vec2 dir) {
|
|||||||
e->body.gravity_scale = 0.0f;
|
e->body.gravity_scale = 0.0f;
|
||||||
e->health = 9999; /* indestructible */
|
e->health = 9999; /* indestructible */
|
||||||
e->max_health = 9999;
|
e->max_health = 9999;
|
||||||
e->flags |= ENTITY_INVINCIBLE;
|
e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;
|
||||||
e->damage = 0;
|
e->damage = 0;
|
||||||
|
|
||||||
MovingPlatData *md = calloc(1, sizeof(MovingPlatData));
|
MovingPlatData *md = calloc(1, sizeof(MovingPlatData));
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ void level_update(Level *level, float dt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Update all entities */
|
/* Update all entities */
|
||||||
entity_update_all(&level->entities, dt, &level->map);
|
entity_update_all(&level->entities, dt, &level->map, &level->camera);
|
||||||
|
|
||||||
/* Handle collisions (only meaningful once player exists) */
|
/* Handle collisions (only meaningful once player exists) */
|
||||||
if (level->player_spawned) {
|
if (level->player_spawned) {
|
||||||
|
|||||||
@@ -632,6 +632,7 @@ Entity *player_spawn(EntityManager *em, Vec2 pos) {
|
|||||||
e->body.gravity_scale = 1.0f;
|
e->body.gravity_scale = 1.0f;
|
||||||
e->health = 3;
|
e->health = 3;
|
||||||
e->max_health = 3;
|
e->max_health = 3;
|
||||||
|
e->flags |= ENTITY_ALWAYS_UPDATE;
|
||||||
|
|
||||||
PlayerData *pd = calloc(1, sizeof(PlayerData));
|
PlayerData *pd = calloc(1, sizeof(PlayerData));
|
||||||
pd->has_gun = true; /* armed by default; moon level overrides */
|
pd->has_gun = true; /* armed by default; moon level overrides */
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ Entity *spacecraft_spawn(EntityManager *em, Vec2 land_pos) {
|
|||||||
e->health = 999;
|
e->health = 999;
|
||||||
e->max_health = 999;
|
e->max_health = 999;
|
||||||
e->damage = 0;
|
e->damage = 0;
|
||||||
e->flags |= ENTITY_INVINCIBLE;
|
e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;
|
||||||
|
|
||||||
SpacecraftData *sd = calloc(1, sizeof(SpacecraftData));
|
SpacecraftData *sd = calloc(1, sizeof(SpacecraftData));
|
||||||
if (!sd) {
|
if (!sd) {
|
||||||
@@ -369,7 +369,7 @@ Entity *spacecraft_spawn_exit(EntityManager *em, Vec2 land_pos) {
|
|||||||
e->health = 999;
|
e->health = 999;
|
||||||
e->max_health = 999;
|
e->max_health = 999;
|
||||||
e->damage = 0;
|
e->damage = 0;
|
||||||
e->flags |= ENTITY_INVINCIBLE;
|
e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;
|
||||||
|
|
||||||
SpacecraftData *sd = calloc(1, sizeof(SpacecraftData));
|
SpacecraftData *sd = calloc(1, sizeof(SpacecraftData));
|
||||||
if (!sd) {
|
if (!sd) {
|
||||||
|
|||||||
Reference in New Issue
Block a user