Add pause menu, laser turret, charger/spawner enemies, and Mars campaign

Implement four feature phases:

Phase 1 - Pause menu: extract bitmap font into shared engine/font
module, add MODE_PAUSED with Resume/Restart/Quit overlay.

Phase 2 - Laser turret hazard: ENT_LASER_TURRET with charge/fire/
cooldown state machine, per-pixel beam raycast, two variants (fixed
and tracking). Registered in entity registry with editor icons.

Phase 3 - Charger and Spawner enemies: charger ground patrol with
detect/telegraph/charge/stun cycle (2s charge timeout), spawner that
periodically creates grunts up to a global cap of 3.

Phase 4 - Mars campaign: two handcrafted levels (mars01 surface,
mars02 base), mars_tileset.png, PARALLAX_STYLE_MARS with salmon sky
and red mesas, THEME_MARS_SURFACE/THEME_MARS_BASE for the procedural
generator with per-theme gravity/tileset/parallax. Moon campaign now
chains moon03 -> mars01 -> mars02 -> victory.

Also fix review findings: deterministic seed on generated level
restart, NULL checks on calloc in spawn functions, charge timeout
to prevent infinite charge on flat terrain, and stop suppressing
stderr in Makefile web-serve target so real errors are visible.
This commit is contained in:
Thomas
2026-03-02 19:34:12 +00:00
parent e5e91247fe
commit d0853fb38d
22 changed files with 1519 additions and 147 deletions

View File

@@ -44,4 +44,52 @@ typedef struct FlyerData {
void flyer_register(EntityManager *em);
Entity *flyer_spawn(EntityManager *em, Vec2 pos);
/* ── Charger enemy ─────────────────────────────────── */
/* Ground patrol that detects the player, telegraphs */
/* briefly, then charges at high speed. Stuns on wall. */
#define CHARGER_WIDTH 14
#define CHARGER_HEIGHT 16
#define CHARGER_PATROL_SPEED 30.0f /* slow patrol (px/s) */
#define CHARGER_CHARGE_SPEED 150.0f /* charge rush speed (px/s) */
#define CHARGER_DETECT_RANGE 200.0f /* horizontal detect (px) */
#define CHARGER_CHARGE_TIME 2.0f /* max charge duration (s) */
#define CHARGER_HEALTH 2
typedef enum ChargerState {
CHARGER_PATROL,
CHARGER_ALERT, /* telegraph before charging */
CHARGER_CHARGE, /* full-speed horizontal rush */
CHARGER_STUNNED, /* hit a wall, briefly vulnerable */
} ChargerState;
typedef struct ChargerData {
ChargerState state;
float patrol_dir; /* 1.0 or -1.0 */
float state_timer; /* countdown for current state */
float death_timer;
} ChargerData;
void charger_register(EntityManager *em);
Entity *charger_spawn(EntityManager *em, Vec2 pos);
/* ── Spawner enemy ─────────────────────────────────── */
/* Stationary, periodically spawns grunt enemies up to */
/* a cap. Destructible. */
#define SPAWNER_WIDTH 16
#define SPAWNER_HEIGHT 16
#define SPAWNER_HEALTH 3
#define SPAWNER_INTERVAL 4.5f /* seconds between spawns */
#define SPAWNER_MAX_ALIVE 3 /* max grunts alive at once */
typedef struct SpawnerData {
float spawn_timer; /* countdown to next spawn */
float death_timer;
float pulse_timer; /* visual pulse before spawn */
} SpawnerData;
void spawner_register(EntityManager *em);
Entity *spawner_spawn(EntityManager *em, Vec2 pos);
#endif /* JNR_ENEMY_H */