Add in-game level editor with auto-discovered tile/entity palettes

Implements a full level editor that runs inside the game engine as an
alternative mode, accessible via --edit flag or E key during gameplay.
The editor auto-discovers available tiles from the tileset texture and
entities from a new central registry, so adding new game content
automatically appears in the editor without any editor-specific changes.

Editor features: tile painting (pencil/eraser/flood fill) across 3
layers, entity placement with drag-to-move, player spawn point tool,
camera pan/zoom, grid overlay, .lvl save/load, map resize, and test
play (P to play, ESC to return to editor).

Supporting changes:
- Entity registry centralizes spawn functions (replaces strcmp chain)
- Mouse input + raw keyboard access added to input system
- Camera zoom support for editor overview
- Zoom-aware rendering in tilemap, renderer, and sprite systems
- Powerup and drone sprites/animations wired up (were defined but unused)
- Bitmap font renderer for editor UI (4x6 pixel glyphs, no dependencies)
This commit is contained in:
Thomas
2026-02-28 20:24:43 +00:00
parent c66c12ae68
commit ea6e16358f
30 changed files with 4959 additions and 51 deletions

View File

@@ -505,6 +505,361 @@ static const uint32_t enemy_bullet2[16*16] = {
};
/* ── Hazard sprites ─────────────────────────────────── */
/* Turret idle - metallic gun turret mounted on wall/floor */
static const uint32_t turret_idle1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, T, T, T,
T, T, T, T, GYD, GRY, GRY, GRY, GRY, GRY, GRY, GRY, GRY, GYD, T, T,
T, T, T, T, GYD, GRY, GYL, GRY, GRY, GYL, GRY, GRY, GRY, GYD, T, T,
T, T, T, GYD, GRY, GRY, GRY, RED, RED, GRY, GRY, GRY, GRY, GRY, GYD, T,
T, T, T, GYD, GRY, GRY, GRY, RDD, RDD, GRY, GRY, GRY, GRY, GRY, GYD, T,
T, T, T, T, GYD, GRY, GYL, GRY, GRY, GYL, GRY, GRY, GRY, GYD, T, T,
T, T, T, T, GYD, GRY, GRY, GRY, GRY, GRY, GRY, GRY, GRY, GYD, T, T,
T, T, T, T, T, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, T, T, T,
T, T, T, T, T, T, GYD, GRY, GRY, GYD, T, T, T, T, T, T,
T, T, T, T, T, GYD, GRY, GRY, GRY, GRY, GYD, T, T, T, T, T,
T, T, T, T, GYD, GRY, GRY, GRY, GRY, GRY, GRY, GYD, T, T, T, T,
T, T, T, T, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, T, T, T, T,
};
/* Turret fire - barrel flash */
static const uint32_t turret_fire1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, ORG, T, T,
T, T, T, T, T, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, YLW, ORG, T,
T, T, T, T, GYD, GRY, GRY, GRY, GRY, GRY, GRY, GRY, GRY, WHT, YLW, T,
T, T, T, T, GYD, GRY, GYL, GRY, GRY, GYL, GRY, GRY, GRY, WHT, YLW, T,
T, T, T, GYD, GRY, GRY, GRY, RED, RED, GRY, GRY, GRY, GRY, YLW, ORG, T,
T, T, T, GYD, GRY, GRY, GRY, RDD, RDD, GRY, GRY, GRY, GRY, YLW, ORG, T,
T, T, T, T, GYD, GRY, GYL, GRY, GRY, GYL, GRY, GRY, GRY, WHT, YLW, T,
T, T, T, T, GYD, GRY, GRY, GRY, GRY, GRY, GRY, GRY, GRY, WHT, YLW, T,
T, T, T, T, T, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, YLW, ORG, T,
T, T, T, T, T, T, GYD, GRY, GRY, GYD, T, T, T, ORG, T, T,
T, T, T, T, T, GYD, GRY, GRY, GRY, GRY, GYD, T, T, T, T, T,
T, T, T, T, GYD, GRY, GRY, GRY, GRY, GRY, GRY, GYD, T, T, T, T,
T, T, T, T, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, T, T, T, T,
};
/* Moving platform - metallic platform with glowing edges */
#define PLT 0x3a6ea5FF /* platform blue */
#define PLD 0x2a5080FF /* platform blue dark */
#define PLL 0x5a9ed5FF /* platform blue light */
static const uint32_t platform1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN, CYN,
PLL, PLL, PLT, PLT, PLT, PLT, PLT, PLT, PLT, PLT, PLT, PLT, PLT, PLT, PLL, PLL,
PLT, PLT, PLT, PLD, PLT, PLT, PLD, PLT, PLT, PLD, PLT, PLT, PLD, PLT, PLT, PLT,
PLT, PLT, PLT, PLD, PLT, PLT, PLD, PLT, PLT, PLD, PLT, PLT, PLD, PLT, PLT, PLT,
PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD, PLD,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Flame vent base - metallic grate on floor */
#define FLB 0x2244aaFF /* flame blue */
#define FLD 0x1a3388FF /* flame blue dark */
#define FLL 0x4488ffFF /* flame blue light */
static const uint32_t flame_vent_idle1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FLB, FLB, T, T, T, T, T, T, T,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD,
GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY,
GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
};
/* Flame vent active - blue flames shooting up from grate */
static const uint32_t flame_vent_active1[16*16] = {
T, T, T, T, T, T, FLD, T, T, FLD, T, T, T, T, T, T,
T, T, T, T, T, FLD, FLB, FLD, FLD, FLB, FLD, T, T, T, T, T,
T, T, T, T, FLD, FLB, FLL, FLB, FLB, FLL, FLB, FLD, T, T, T, T,
T, T, T, FLD, FLB, FLL, WHT, FLL, FLL, WHT, FLL, FLB, FLD, T, T, T,
T, T, T, FLB, FLL, WHT, WHT, FLL, FLL, WHT, WHT, FLL, FLB, T, T, T,
T, T, FLD, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, FLD, T, T,
T, T, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, T, T,
T, T, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, T, T,
T, T, FLD, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, FLD, T, T,
T, T, T, FLB, FLL, FLL, WHT, WHT, WHT, WHT, FLL, FLL, FLB, T, T, T,
T, T, T, T, FLB, FLL, FLL, FLB, FLB, FLL, FLL, FLB, T, T, T, T,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD,
GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY,
GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
};
/* Flame vent active frame 2 - flame flicker */
static const uint32_t flame_vent_active2[16*16] = {
T, T, T, T, T, T, T, FLD, FLD, T, T, T, T, T, T, T,
T, T, T, T, T, T, FLD, FLB, FLB, FLD, T, T, T, T, T, T,
T, T, T, T, T, FLD, FLB, FLL, FLL, FLB, FLD, T, T, T, T, T,
T, T, T, T, FLD, FLB, FLL, WHT, WHT, FLL, FLB, FLD, T, T, T, T,
T, T, T, FLD, FLB, FLL, WHT, WHT, WHT, WHT, FLL, FLB, FLD, T, T, T,
T, T, T, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, T, T, T,
T, T, FLD, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, FLD, T, T,
T, T, FLB, FLL, WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLB, T, T,
T, T, FLB, FLL, FLL, WHT, WHT, WHT, WHT, WHT, WHT, FLL, FLL, FLB, T, T,
T, T, T, FLB, FLL, FLL, WHT, WHT, WHT, WHT, FLL, FLL, FLB, T, T, T,
T, T, T, T, FLB, FLB, FLL, FLB, FLB, FLL, FLB, FLB, T, T, T, T,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD,
GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY,
GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD, GRY, GYD,
GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD, GYD,
};
/* Force field on - vertical energy barrier */
#define FFB 0x4466ffFF /* force field blue */
#define FFD 0x2244ccFF /* force field dark */
#define FFL 0x88aaFFFF /* force field light */
static const uint32_t force_field_on1[16*16] = {
T, T, T, FFD, T, FFD, FFB, FFL, FFL, FFB, FFD, T, FFD, T, T, T,
T, T, FFD, FFB, FFD, FFB, FFL, WHT, WHT, FFL, FFB, FFD, FFB, FFD, T, T,
T, T, T, FFB, FFB, FFL, WHT, WHT, WHT, WHT, FFL, FFB, FFB, T, T, T,
T, T, FFD, FFB, FFL, WHT, WHT, FFL, FFL, WHT, WHT, FFL, FFB, FFD, T, T,
T, T, T, FFB, FFL, WHT, FFL, FFB, FFB, FFL, WHT, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFL, FFB, FFD, FFD, FFB, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, FFB, FFD, FFB, FFB, FFD, FFB, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFD, FFB, FFL, FFL, FFB, FFD, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFD, FFB, FFL, WHT, WHT, FFL, FFB, FFD, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFL, WHT, WHT, WHT, WHT, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, WHT, WHT, FFL, FFL, WHT, WHT, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFL, FFB, FFD, FFD, FFB, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, FFB, FFD, FFB, FFB, FFD, FFB, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFD, FFB, FFL, FFL, FFL, FFL, FFB, FFD, FFB, FFD, T, T,
T, T, T, FFD, T, FFD, FFB, FFL, FFL, FFB, FFD, T, FFD, T, T, T,
T, T, T, T, T, T, FFD, FFD, FFD, FFD, T, T, T, T, T, T,
};
/* Force field on frame 2 - shimmer */
static const uint32_t force_field_on2[16*16] = {
T, T, T, T, T, FFD, FFB, FFB, FFB, FFB, FFD, T, T, T, T, T,
T, T, T, FFD, FFD, FFB, FFL, FFL, FFL, FFL, FFB, FFD, FFD, T, T, T,
T, T, FFD, FFB, FFB, FFL, WHT, FFL, FFL, WHT, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, WHT, FFL, FFB, FFB, FFL, WHT, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFL, FFB, FFD, FFD, FFB, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, FFB, FFD, FFB, FFB, FFD, FFB, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFD, FFB, FFL, FFL, FFB, FFD, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFD, FFB, FFL, WHT, WHT, FFL, FFB, FFD, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFL, WHT, WHT, WHT, WHT, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, WHT, FFL, FFB, FFB, FFL, WHT, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFB, FFL, FFB, FFD, FFD, FFB, FFL, FFB, FFB, FFD, T, T,
T, T, T, FFB, FFL, FFB, FFD, FFB, FFB, FFD, FFB, FFL, FFB, T, T, T,
T, T, FFD, FFB, FFD, FFB, FFL, FFL, FFL, FFL, FFB, FFD, FFB, FFD, T, T,
T, T, T, FFD, T, FFD, FFB, FFL, FFL, FFB, FFD, T, FFD, T, T, T,
T, T, T, T, T, T, FFD, FFB, FFB, FFD, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
};
/* Force field off - just dim posts/emitters */
static const uint32_t force_field_off1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, FFD, FFD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* ── Powerup sprites ────────────────────────────────── */
/* Health powerup — red cross / heart icon */
static const uint32_t powerup_health1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, RDL, RDL, T, T, RDL, RDL, T, T, T, T, T,
T, T, T, T, RDL, RED, RED, RDL, RDL, RED, RED, RDL, T, T, T, T,
T, T, T, RDL, RED, RED, RED, RED, RED, RED, RED, RED, RDL, T, T, T,
T, T, T, RDL, RED, RED, WHT, RED, RED, WHT, RED, RED, RDL, T, T, T,
T, T, T, RDL, RED, RED, RED, WHT, WHT, RED, RED, RED, RDL, T, T, T,
T, T, T, RDL, RED, RED, RED, RED, RED, RED, RED, RED, RDL, T, T, T,
T, T, T, T, RDL, RED, RED, RED, RED, RED, RED, RDL, T, T, T, T,
T, T, T, T, T, RDL, RED, RED, RED, RED, RDL, T, T, T, T, T,
T, T, T, T, T, T, RDL, RED, RED, RDL, T, T, T, T, T, T,
T, T, T, T, T, T, T, RDD, RDD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Health powerup frame 2 — slight glow/pulse */
static const uint32_t powerup_health2[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, RDL, RDL, T, T, RDL, RDL, T, T, T, T, T,
T, T, T, T, RDL, RED, RED, RDL, RDL, RED, RED, RDL, T, T, T, T,
T, T, T, RDL, RED, RED, RED, RED, RED, RED, RED, RED, RDL, T, T, T,
T, T, RDL, RED, RED, RED, RED, RED, RED, RED, RED, RED, RED, RDL, T, T,
T, T, RDL, RED, RED, RED, WHT, WHT, WHT, WHT, RED, RED, RED, RDL, T, T,
T, T, RDL, RED, RED, WHT, WHT, WHT, WHT, WHT, WHT, RED, RED, RDL, T, T,
T, T, RDL, RED, RED, RED, WHT, WHT, WHT, WHT, RED, RED, RED, RDL, T, T,
T, T, T, RDL, RED, RED, RED, RED, RED, RED, RED, RED, RDL, T, T, T,
T, T, T, T, RDL, RED, RED, RED, RED, RED, RED, RDL, T, T, T, T,
T, T, T, T, T, RDL, RED, RED, RED, RED, RDL, T, T, T, T, T,
T, T, T, T, T, T, RDD, RDD, RDD, RDD, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Jetpack recharge powerup — orange lightning bolt */
static const uint32_t powerup_jetpack1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, ORG, ORG, ORG, T, T, T, T, T, T,
T, T, T, T, T, T, ORG, YLW, YLW, ORG, T, T, T, T, T, T,
T, T, T, T, T, ORG, YLW, YLW, ORG, T, T, T, T, T, T, T,
T, T, T, T, ORG, YLW, YLW, ORG, T, T, T, T, T, T, T, T,
T, T, T, ORG, YLW, WHT, YLW, ORG, ORG, ORG, ORG, T, T, T, T, T,
T, T, T, ORG, YLW, WHT, WHT, YLW, YLW, YLW, ORG, T, T, T, T, T,
T, T, T, T, ORG, ORG, ORG, YLW, WHT, YLW, ORG, T, T, T, T, T,
T, T, T, T, T, T, T, ORG, YLW, ORG, T, T, T, T, T, T,
T, T, T, T, T, T, ORG, YLW, ORG, T, T, T, T, T, T, T,
T, T, T, T, T, ORG, YLW, ORG, T, T, T, T, T, T, T, T,
T, T, T, T, T, ORG, ORG, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Jetpack powerup frame 2 — brighter glow */
static const uint32_t powerup_jetpack2[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, YLW, YLW, YLW, T, T, T, T, T, T,
T, T, T, T, T, T, YLW, WHT, WHT, YLW, T, T, T, T, T, T,
T, T, T, T, T, YLW, WHT, WHT, YLW, T, T, T, T, T, T, T,
T, T, T, T, YLW, WHT, WHT, YLW, T, T, T, T, T, T, T, T,
T, T, T, YLW, WHT, WHT, WHT, YLW, YLW, YLW, YLW, T, T, T, T, T,
T, T, T, YLW, WHT, WHT, WHT, WHT, WHT, WHT, YLW, T, T, T, T, T,
T, T, T, T, YLW, YLW, YLW, WHT, WHT, WHT, YLW, T, T, T, T, T,
T, T, T, T, T, T, T, YLW, WHT, YLW, T, T, T, T, T, T,
T, T, T, T, T, T, YLW, WHT, YLW, T, T, T, T, T, T, T,
T, T, T, T, T, YLW, WHT, YLW, T, T, T, T, T, T, T, T,
T, T, T, T, T, YLW, YLW, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Drone powerup — cyan spinning gear/orb */
static const uint32_t powerup_drone1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, CYD, CYD, T, T, T, T, T, T, T,
T, T, T, T, T, CYD, CYN, CYN, CYN, CYN, CYD, T, T, T, T, T,
T, T, T, T, CYD, CYN, CYN, WHT, WHT, CYN, CYN, CYD, T, T, T, T,
T, T, T, CYD, CYN, CYN, WHT, WHT, WHT, WHT, CYN, CYN, CYD, T, T, T,
T, T, T, CYN, CYN, WHT, WHT, CYN, CYN, WHT, WHT, CYN, CYN, T, T, T,
T, T, CYD, CYN, WHT, WHT, CYN, CYD, CYD, CYN, WHT, WHT, CYN, CYD, T, T,
T, T, CYD, CYN, WHT, WHT, CYN, CYD, CYD, CYN, WHT, WHT, CYN, CYD, T, T,
T, T, T, CYN, CYN, WHT, WHT, CYN, CYN, WHT, WHT, CYN, CYN, T, T, T,
T, T, T, CYD, CYN, CYN, WHT, WHT, WHT, WHT, CYN, CYN, CYD, T, T, T,
T, T, T, T, CYD, CYN, CYN, WHT, WHT, CYN, CYN, CYD, T, T, T, T,
T, T, T, T, T, CYD, CYN, CYN, CYN, CYN, CYD, T, T, T, T, T,
T, T, T, T, T, T, T, CYD, CYD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Drone powerup frame 2 — rotated highlight */
static const uint32_t powerup_drone2[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, CYD, CYD, T, T, T, T, T, T, T,
T, T, T, T, T, CYD, CYN, CYN, CYN, CYN, CYD, T, T, T, T, T,
T, T, T, T, CYD, CYN, WHT, WHT, CYN, CYN, CYN, CYD, T, T, T, T,
T, T, T, CYD, CYN, WHT, WHT, CYN, CYN, CYN, CYN, CYN, CYD, T, T, T,
T, T, T, CYN, WHT, WHT, CYN, CYD, CYD, CYN, CYN, CYN, CYN, T, T, T,
T, T, CYD, CYN, WHT, CYN, CYD, CYD, CYD, CYD, CYN, WHT, CYN, CYD, T, T,
T, T, CYD, CYN, WHT, CYN, CYD, CYD, CYD, CYD, CYN, WHT, CYN, CYD, T, T,
T, T, T, CYN, CYN, CYN, CYN, CYD, CYD, CYN, WHT, WHT, CYN, T, T, T,
T, T, T, CYD, CYN, CYN, CYN, CYN, CYN, WHT, WHT, CYN, CYD, T, T, T,
T, T, T, T, CYD, CYN, CYN, CYN, WHT, WHT, CYN, CYD, T, T, T, T,
T, T, T, T, T, CYD, CYN, CYN, CYN, CYN, CYD, T, T, T, T, T,
T, T, T, T, T, T, T, CYD, CYD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* ── Drone companion sprite ────────────────────────── */
/* Small hovering drone — metallic with cyan thruster */
static const uint32_t drone_frame1[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, GYL, GYL, GYL, GYL, T, T, T, T, T, T,
T, T, T, T, T, GYL, GRY, GRY, GRY, GRY, GYL, T, T, T, T, T,
T, T, T, T, GYL, GRY, GRY, CYN, CYN, GRY, GRY, GYL, T, T, T, T,
T, T, T, T, GYD, GRY, CYN, WHT, WHT, CYN, GRY, GYD, T, T, T, T,
T, T, T, T, GYD, GRY, CYN, WHT, WHT, CYN, GRY, GYD, T, T, T, T,
T, T, T, T, GYL, GRY, GRY, CYN, CYN, GRY, GRY, GYL, T, T, T, T,
T, T, T, T, T, GYD, GRY, GRY, GRY, GRY, GYD, T, T, T, T, T,
T, T, T, T, T, T, GYD, CYD, CYD, GYD, T, T, T, T, T, T,
T, T, T, T, T, T, T, CYN, CYN, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* Drone frame 2 — thruster flicker */
static const uint32_t drone_frame2[16*16] = {
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, GYL, GYL, GYL, GYL, T, T, T, T, T, T,
T, T, T, T, T, GYL, GRY, GRY, GRY, GRY, GYL, T, T, T, T, T,
T, T, T, T, GYL, GRY, GRY, CYN, CYN, GRY, GRY, GYL, T, T, T, T,
T, T, T, T, GYD, GRY, CYN, WHT, WHT, CYN, GRY, GYD, T, T, T, T,
T, T, T, T, GYD, GRY, CYN, WHT, WHT, CYN, GRY, GYD, T, T, T, T,
T, T, T, T, GYL, GRY, GRY, CYN, CYN, GRY, GRY, GYL, T, T, T, T,
T, T, T, T, T, GYD, GRY, GRY, GRY, GRY, GYD, T, T, T, T, T,
T, T, T, T, T, T, CYD, CYN, CYN, CYD, T, T, T, T, T, T,
T, T, T, T, T, T, CYN, WHT, WHT, CYN, T, T, T, T, T, T,
T, T, T, T, T, T, T, CYD, CYD, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,
};
/* ── Spritesheet generation ────────────────────────── */
/* All sprite definitions for the sheet - row, column, pixel data */
@@ -551,10 +906,33 @@ static const SpriteDef s_sprite_defs[] = {
{3, 4, impact3},
{3, 5, enemy_bullet1},
{3, 6, enemy_bullet2},
/* Row 4: Hazards */
{4, 0, turret_idle1},
{4, 1, turret_fire1},
{4, 2, platform1},
{4, 3, flame_vent_idle1},
{4, 4, flame_vent_active1},
{4, 5, flame_vent_active2},
{4, 6, force_field_on1},
{4, 7, force_field_on2},
/* Row 5: Hazards continued + Powerups */
{5, 0, force_field_off1},
{5, 1, powerup_health1},
{5, 2, powerup_health2},
{5, 3, powerup_jetpack1},
{5, 4, powerup_jetpack2},
{5, 5, powerup_drone1},
{5, 6, powerup_drone2},
/* Row 6: Drone companion */
{6, 0, drone_frame1},
{6, 1, drone_frame2},
};
#define SHEET_COLS 8
#define SHEET_ROWS 4
#define SHEET_ROWS 7
SDL_Texture *sprites_generate(SDL_Renderer *renderer) {
int w = SHEET_COLS * SPRITE_CELL;
@@ -690,6 +1068,59 @@ static AnimFrame s_enemy_bullet_frames[] = {
FRAME(6, 3, 0.1f),
};
/* Hazard animations */
static AnimFrame s_turret_idle_frames[] = {
FRAME(0, 4, 1.0f),
};
static AnimFrame s_turret_fire_frames[] = {
FRAME(1, 4, 0.15f),
};
static AnimFrame s_platform_frames[] = {
FRAME(2, 4, 1.0f),
};
static AnimFrame s_flame_vent_idle_frames[] = {
FRAME(3, 4, 1.0f),
};
static AnimFrame s_flame_vent_active_frames[] = {
FRAME(4, 4, 0.08f),
FRAME(5, 4, 0.08f),
};
static AnimFrame s_force_field_on_frames[] = {
FRAME(6, 4, 0.15f),
FRAME(7, 4, 0.15f),
};
static AnimFrame s_force_field_off_frames[] = {
FRAME(0, 5, 1.0f),
};
/* Powerups */
static AnimFrame s_powerup_health_frames[] = {
FRAME(1, 5, 0.4f),
FRAME(2, 5, 0.4f),
};
static AnimFrame s_powerup_jetpack_frames[] = {
FRAME(3, 5, 0.3f),
FRAME(4, 5, 0.3f),
};
static AnimFrame s_powerup_drone_frames[] = {
FRAME(5, 5, 0.3f),
FRAME(6, 5, 0.3f),
};
/* Drone companion */
static AnimFrame s_drone_frames[] = {
FRAME(0, 6, 0.2f),
FRAME(1, 6, 0.2f),
};
/* Exported animation definitions */
AnimDef anim_player_idle;
AnimDef anim_player_run;
@@ -708,6 +1139,20 @@ AnimDef anim_bullet;
AnimDef anim_bullet_impact;
AnimDef anim_enemy_bullet;
AnimDef anim_turret_idle;
AnimDef anim_turret_fire;
AnimDef anim_platform;
AnimDef anim_flame_vent_idle;
AnimDef anim_flame_vent_active;
AnimDef anim_force_field_on;
AnimDef anim_force_field_off;
AnimDef anim_powerup_health;
AnimDef anim_powerup_jetpack;
AnimDef anim_powerup_drone;
AnimDef anim_drone;
void sprites_init_anims(void) {
anim_player_idle = (AnimDef){s_player_idle_frames, 2, true, NULL};
anim_player_run = (AnimDef){s_player_run_frames, 4, true, NULL};
@@ -725,4 +1170,18 @@ void sprites_init_anims(void) {
anim_bullet = (AnimDef){s_bullet_frames, 2, true, NULL};
anim_bullet_impact = (AnimDef){s_impact_frames, 3, false, NULL};
anim_enemy_bullet = (AnimDef){s_enemy_bullet_frames, 2, true, NULL};
anim_turret_idle = (AnimDef){s_turret_idle_frames, 1, true, NULL};
anim_turret_fire = (AnimDef){s_turret_fire_frames, 1, false, NULL};
anim_platform = (AnimDef){s_platform_frames, 1, true, NULL};
anim_flame_vent_idle = (AnimDef){s_flame_vent_idle_frames, 1, true, NULL};
anim_flame_vent_active = (AnimDef){s_flame_vent_active_frames, 2, true, NULL};
anim_force_field_on = (AnimDef){s_force_field_on_frames, 2, true, NULL};
anim_force_field_off = (AnimDef){s_force_field_off_frames, 1, true, NULL};
anim_powerup_health = (AnimDef){s_powerup_health_frames, 2, true, NULL};
anim_powerup_jetpack = (AnimDef){s_powerup_jetpack_frames, 2, true, NULL};
anim_powerup_drone = (AnimDef){s_powerup_drone_frames, 2, true, NULL};
anim_drone = (AnimDef){s_drone_frames, 2, true, NULL};
}