diff --git a/src/game/enemy.c b/src/game/enemy.c index a608110..7e079a2 100644 --- a/src/game/enemy.c +++ b/src/game/enemy.c @@ -291,6 +291,8 @@ static void charger_update(Entity *self, float dt, const Tilemap *map) { /* Death sequence */ if (self->flags & ENTITY_DEAD) { + animation_set(&self->anim, &anim_charger_death); + animation_update(&self->anim, dt); cd->death_timer -= dt; body->vel.x = 0; if (cd->death_timer <= 0) { @@ -394,43 +396,63 @@ static void charger_update(Entity *self, float dt, const Tilemap *map) { } } - /* Animation — use grunt anims as placeholder */ - if (cd->state == CHARGER_CHARGE) { - animation_set(&self->anim, &anim_grunt_walk); + /* Animation */ + if (cd->state == CHARGER_CHARGE || cd->state == CHARGER_PATROL) { + animation_set(&self->anim, &anim_charger_walk); } else { - animation_set(&self->anim, &anim_grunt_idle); + animation_set(&self->anim, &anim_charger_idle); } animation_update(&self->anim, dt); } static void charger_render(Entity *self, const Camera *cam) { + (void)cam; Body *body = &self->body; ChargerData *cd = (ChargerData *)self->data; - /* Colored rect fallback — orange/amber color scheme */ - SDL_Color color; - if (cd && cd->state == CHARGER_ALERT) { - /* Flash during telegraph */ - float blink = sinf(cd->state_timer * 30.0f); - uint8_t r = (blink > 0) ? 255 : 200; - color = (SDL_Color){r, 160, 40, 255}; - } else if (cd && cd->state == CHARGER_STUNNED) { - color = (SDL_Color){160, 160, 100, 255}; - } else if (cd && cd->state == CHARGER_CHARGE) { - color = (SDL_Color){255, 120, 30, 255}; - } else { - color = (SDL_Color){220, 140, 40, 255}; - } - renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam); + if (g_spritesheet && self->anim.def) { + SDL_Rect src = animation_current_rect(&self->anim); - /* Direction indicator: small triangle in facing direction */ - float cx = body->pos.x + body->size.x * 0.5f; - float cy = body->pos.y + 2.0f; - bool left = (self->flags & ENTITY_FACING_LEFT) != 0; - float arrow_x = left ? body->pos.x - 3.0f : body->pos.x + body->size.x + 1.0f; - renderer_draw_rect(vec2(arrow_x, cy), vec2(2, 3), - (SDL_Color){255, 200, 60, 255}, LAYER_ENTITIES, cam); - (void)cx; + /* Center the 16x16 sprite over the 14x16 hitbox */ + Vec2 render_pos = vec2( + body->pos.x - 1.0f, + body->pos.y + ); + + /* Flash white during alert telegraph */ + uint8_t alpha = 255; + if (cd && cd->state == CHARGER_ALERT) { + float blink = sinf(cd->state_timer * 30.0f); + alpha = (blink > 0) ? 180 : 255; + } + + Sprite spr = { + .texture = g_spritesheet, + .src = src, + .pos = render_pos, + .size = vec2(SPRITE_CELL, SPRITE_CELL), + .flip_x = (self->flags & ENTITY_FACING_LEFT) != 0, + .flip_y = false, + .layer = LAYER_ENTITIES, + .alpha = alpha, + }; + renderer_submit(&spr); + } else { + /* Colored rect fallback */ + SDL_Color color; + if (cd && cd->state == CHARGER_ALERT) { + float blink = sinf(cd->state_timer * 30.0f); + uint8_t r = (blink > 0) ? 255 : 200; + color = (SDL_Color){r, 160, 40, 255}; + } else if (cd && cd->state == CHARGER_STUNNED) { + color = (SDL_Color){160, 160, 100, 255}; + } else if (cd && cd->state == CHARGER_CHARGE) { + color = (SDL_Color){255, 120, 30, 255}; + } else { + color = (SDL_Color){220, 140, 40, 255}; + } + renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam); + } } static void charger_destroy(Entity *self) { @@ -494,6 +516,8 @@ static void spawner_update(Entity *self, float dt, const Tilemap *map) { /* Death sequence */ if (self->flags & ENTITY_DEAD) { + animation_set(&self->anim, &anim_spawner_death); + animation_update(&self->anim, dt); sd->death_timer -= dt; if (sd->death_timer <= 0) { particle_emit_death_puff(self->body.pos, (SDL_Color){180, 60, 180, 255}); @@ -511,6 +535,10 @@ static void spawner_update(Entity *self, float dt, const Tilemap *map) { sd->pulse_timer = 0; } + /* Animation */ + animation_set(&self->anim, &anim_spawner_idle); + animation_update(&self->anim, dt); + /* Spawn a grunt when timer expires */ if (sd->spawn_timer <= 0) { sd->spawn_timer = SPAWNER_INTERVAL; @@ -530,26 +558,46 @@ static void spawner_update(Entity *self, float dt, const Tilemap *map) { } static void spawner_render(Entity *self, const Camera *cam) { + (void)cam; Body *body = &self->body; SpawnerData *sd = (SpawnerData *)self->data; - /* Pulsing purple color when about to spawn */ - SDL_Color color; - if (sd && sd->pulse_timer > 0) { - float pulse = sinf(sd->pulse_timer * 12.0f) * 0.5f + 0.5f; - uint8_t r = (uint8_t)(140 + 80 * pulse); - uint8_t b = (uint8_t)(180 + 60 * pulse); - color = (SDL_Color){r, 50, b, 255}; - } else { - color = (SDL_Color){140, 50, 160, 255}; - } - renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam); + if (g_spritesheet && self->anim.def) { + SDL_Rect src = animation_current_rect(&self->anim); - /* Small inner dot to distinguish from other hazards */ - float cx = body->pos.x + body->size.x * 0.5f - 2.0f; - float cy = body->pos.y + body->size.y * 0.5f - 2.0f; - SDL_Color dot = {255, 200, 255, 255}; - renderer_draw_rect(vec2(cx, cy), vec2(4, 4), dot, LAYER_ENTITIES, cam); + Vec2 render_pos = vec2(body->pos.x, body->pos.y); + + /* Pulse brightness when about to spawn */ + uint8_t alpha = 255; + if (sd && sd->pulse_timer > 0) { + float pulse = sinf(sd->pulse_timer * 12.0f); + alpha = (pulse > 0) ? 200 : 255; + } + + Sprite spr = { + .texture = g_spritesheet, + .src = src, + .pos = render_pos, + .size = vec2(SPRITE_CELL, SPRITE_CELL), + .flip_x = false, + .flip_y = false, + .layer = LAYER_ENTITIES, + .alpha = alpha, + }; + renderer_submit(&spr); + } else { + /* Colored rect fallback */ + SDL_Color color; + if (sd && sd->pulse_timer > 0) { + float pulse = sinf(sd->pulse_timer * 12.0f) * 0.5f + 0.5f; + uint8_t r = (uint8_t)(140 + 80 * pulse); + uint8_t b = (uint8_t)(180 + 60 * pulse); + color = (SDL_Color){r, 50, b, 255}; + } else { + color = (SDL_Color){140, 50, 160, 255}; + } + renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam); + } } static void spawner_destroy(Entity *self) { diff --git a/src/game/laser_turret.c b/src/game/laser_turret.c index 9885423..d96ae7e 100644 --- a/src/game/laser_turret.c +++ b/src/game/laser_turret.c @@ -199,6 +199,14 @@ static void laser_turret_update(Entity *self, float dt, const Tilemap *map) { (SDL_Color){255, 100, 50, 255}); } } + + /* Animation */ + if (ld->state == LASER_CHARGING || ld->state == LASER_FIRING) { + animation_set(&self->anim, &anim_laser_turret_fire); + } else { + animation_set(&self->anim, &anim_laser_turret_idle); + } + animation_update(&self->anim, dt); } /* ── Render ──────────────────────────────────────── */ @@ -281,24 +289,47 @@ static void laser_turret_render(Entity *self, const Camera *cam) { (int)(3.0f * fade), beam_col); } - /* Draw turret body — colored rect fallback (no sprite yet). */ - SDL_Color base_col; - if (ld->state == LASER_FIRING) { - base_col = (SDL_Color){200, 80, 50, 255}; - } else if (ld->state == LASER_CHARGING) { - base_col = (SDL_Color){180, 100, 60, 255}; - } else { - base_col = (SDL_Color){140, 100, 80, 255}; - } - renderer_draw_rect(body->pos, body->size, base_col, LAYER_ENTITIES, cam); + /* Draw turret body */ + if (g_spritesheet && self->anim.def) { + SDL_Rect src = animation_current_rect(&self->anim); - /* Small dot indicating aim direction */ - float dot_dist = 10.0f; - float dot_x = cx + cosf(ld->aim_angle) * dot_dist - 1.0f; - float dot_y = cy + sinf(ld->aim_angle) * dot_dist - 1.0f; - SDL_Color dot_col = {255, 50, 30, 255}; - renderer_draw_rect(vec2(dot_x, dot_y), vec2(2, 2), dot_col, - LAYER_ENTITIES, cam); + /* Center the 16x16 sprite over the 14x14 hitbox */ + Vec2 render_pos = vec2( + body->pos.x - 1.0f, + body->pos.y - 1.0f + ); + + Sprite spr = { + .texture = g_spritesheet, + .src = src, + .pos = render_pos, + .size = vec2(SPRITE_CELL, SPRITE_CELL), + .flip_x = (self->flags & ENTITY_FACING_LEFT) != 0, + .flip_y = false, + .layer = LAYER_ENTITIES, + .alpha = 255, + }; + renderer_submit(&spr); + } else { + /* Colored rect fallback */ + SDL_Color base_col; + if (ld->state == LASER_FIRING) { + base_col = (SDL_Color){200, 80, 50, 255}; + } else if (ld->state == LASER_CHARGING) { + base_col = (SDL_Color){180, 100, 60, 255}; + } else { + base_col = (SDL_Color){140, 100, 80, 255}; + } + renderer_draw_rect(body->pos, body->size, base_col, LAYER_ENTITIES, cam); + + /* Small dot indicating aim direction (only needed for fallback) */ + float dot_dist = 10.0f; + float dot_x = cx + cosf(ld->aim_angle) * dot_dist - 1.0f; + float dot_y = cy + sinf(ld->aim_angle) * dot_dist - 1.0f; + SDL_Color dot_col = {255, 50, 30, 255}; + renderer_draw_rect(vec2(dot_x, dot_y), vec2(2, 2), dot_col, + LAYER_ENTITIES, cam); + } } /* ── Destroy ─────────────────────────────────────── */ diff --git a/src/game/sprites.c b/src/game/sprites.c index 0d94a00..07ef4a8 100644 --- a/src/game/sprites.c +++ b/src/game/sprites.c @@ -993,6 +993,233 @@ static const uint32_t asteroid2[16*16] = { T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, }; +/* ── Charger enemy sprites ──────────────────────────── */ +/* Charger: Orange armored rhino-like ground charger. 14x16 hitbox. + * Stocky body, forward horn, angry eyes, stubby legs. */ + +#define CHG 0xd98a30FF /* charger orange */ +#define CHD 0xa86620FF /* charger dark */ +#define CHL 0xf0b050FF /* charger light */ +#define CHK 0x7a4a14FF /* charger darkest (outline) */ + +static const uint32_t charger_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, CHK, T, T, T, T, T, CHK, CHK, CHK, T, T, T, T, + T, T, CHK, YLW, CHK, T, T, T, CHK, CHL, CHG, CHG, CHK, T, T, T, + T, T, T, CHK, CHK, CHK, CHK, CHK, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, WHT, BLK, CHG, CHG, WHT, BLK, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, T, CHK, CHG, CHG, CHD, CHD, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHD, CHG, CHG, CHG, CHG, CHD, CHK, T, T, T, T, + T, T, T, T, T, CHK, CHD, CHG, CHG, CHD, CHK, T, T, T, T, T, + T, T, T, T, CHK, CHK, T, T, T, T, CHK, CHK, T, T, T, T, + T, T, T, T, CHK, CHD, T, T, T, T, CHD, CHK, T, T, T, T, + T, T, T, T, CHK, CHK, T, T, T, T, CHK, CHK, T, T, T, T, +}; + +static const uint32_t charger_idle2[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, CHK, T, T, T, T, T, CHK, CHK, CHK, T, T, T, T, + T, T, CHK, YLW, CHK, T, T, T, CHK, CHL, CHG, CHG, CHK, T, T, T, + T, T, T, CHK, CHK, CHK, CHK, CHK, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, WHT, BLK, CHG, CHG, WHT, BLK, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, T, CHK, CHG, CHG, CHD, CHD, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHD, CHG, CHG, CHG, CHG, CHD, CHK, T, T, T, T, + T, T, T, T, T, CHK, CHK, T, T, CHK, CHK, T, T, T, T, T, + T, T, T, T, T, CHD, CHK, T, T, CHK, CHD, T, T, T, T, T, + T, T, T, T, T, CHK, CHK, T, T, CHK, CHK, T, T, T, T, T, +}; + +/* Charger walk frames — legs alternate */ +static const uint32_t charger_walk1[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, CHK, T, T, T, T, T, CHK, CHK, CHK, T, T, T, T, + T, T, CHK, YLW, CHK, T, T, T, CHK, CHL, CHG, CHG, CHK, T, T, T, + T, T, T, CHK, CHK, CHK, CHK, CHK, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, WHT, BLK, CHG, CHG, WHT, BLK, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, T, CHK, CHG, CHG, CHD, CHD, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHD, CHG, CHG, CHG, CHG, CHD, CHK, T, T, T, T, + T, T, T, T, T, CHK, CHD, CHG, CHG, CHD, CHK, T, T, T, T, T, + T, T, T, T, T, CHK, T, T, T, CHK, T, T, T, T, T, T, + T, T, T, T, CHK, T, T, T, T, T, CHK, T, T, T, T, T, + T, T, T, T, CHK, T, T, T, T, T, CHK, T, T, T, T, T, +}; + +static const uint32_t charger_walk2[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, CHK, T, T, T, T, T, CHK, CHK, CHK, T, T, T, T, + T, T, CHK, YLW, CHK, T, T, T, CHK, CHL, CHG, CHG, CHK, T, T, T, + T, T, T, CHK, CHK, CHK, CHK, CHK, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, WHT, BLK, CHG, CHG, WHT, BLK, CHG, CHG, CHK, T, T, + T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, T, T, CHK, CHG, CHG, CHD, CHD, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, T, + T, T, T, T, CHK, CHD, CHG, CHG, CHG, CHG, CHD, CHK, T, T, T, T, + T, T, T, T, T, CHK, CHD, CHG, CHG, CHD, CHK, T, T, T, T, T, + T, T, T, T, CHK, T, T, T, T, T, CHK, T, T, T, T, T, + T, T, T, T, T, CHK, T, T, T, CHK, T, T, T, T, T, T, + T, T, T, T, T, CHK, T, T, T, CHK, T, T, T, T, T, T, +}; + +/* Charger death — flattened, X eyes */ +static const uint32_t charger_death1[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, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, T, T, + T, T, CHK, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHG, CHK, T, T, + T, T, CHK, CHG, BLK, CHG, BLK, CHG, BLK, CHG, BLK, CHG, CHG, CHK, T, T, + T, T, CHK, CHG, CHG, BLK, CHG, CHG, CHG, BLK, CHG, CHG, CHG, CHK, T, T, + T, T, CHK, CHD, CHG, CHG, CHD, CHD, CHD, CHG, CHG, CHD, CHG, CHK, T, T, + T, T, T, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, CHK, T, T, T, +}; + +/* ── Spawner enemy sprites ─────────────────────────── */ +/* Spawner: Purple alien hive pod. 16x16 hitbox. + * Organic, bulbous body with glowing core and tendrils at base. */ + +#define SPW 0xaa44ccFF /* spawner purple */ +#define SPD 0x7b1fa2FF /* spawner dark purple */ +#define SPL 0xce93d8FF /* spawner light purple */ +#define SPK 0x4a1066FF /* spawner outline */ + +static const uint32_t spawner_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, SPK, SPK, SPK, SPK, T, T, T, T, T, T, + T, T, T, T, T, SPK, SPW, SPW, SPW, SPW, SPK, T, T, T, T, T, + T, T, T, T, SPK, SPW, SPW, SPL, SPL, SPW, SPW, SPK, T, T, T, T, + T, T, T, SPK, SPW, SPW, SPL, PRL, PRL, SPL, SPW, SPW, SPK, T, T, T, + T, T, T, SPK, SPW, SPL, PRL, WHT, WHT, PRL, SPL, SPW, SPK, T, T, T, + T, T, SPK, SPW, SPW, SPL, PRL, WHT, WHT, PRL, SPL, SPW, SPW, SPK, T, T, + T, T, SPK, SPW, SPW, SPL, PRL, WHT, WHT, PRL, SPL, SPW, SPW, SPK, T, T, + T, T, T, SPK, SPW, SPW, SPL, PRL, PRL, SPL, SPW, SPW, SPK, T, T, T, + T, T, T, SPK, SPW, SPW, SPW, SPD, SPD, SPW, SPW, SPW, SPK, T, T, T, + T, T, T, T, SPK, SPD, SPW, SPW, SPW, SPW, SPD, SPK, T, T, T, T, + T, T, T, T, SPK, SPK, SPD, SPD, SPD, SPD, SPK, SPK, T, T, T, T, + T, T, T, SPK, SPD, T, SPK, T, T, SPK, T, SPD, SPK, T, T, T, + T, T, SPK, SPD, T, T, T, T, T, T, T, T, SPD, SPK, T, T, + T, T, SPK, T, T, T, T, T, T, T, T, T, T, SPK, T, T, +}; + +/* Spawner idle frame 2 — core pulses brighter */ +static const uint32_t spawner_idle2[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, SPK, SPK, SPK, SPK, T, T, T, T, T, T, + T, T, T, T, T, SPK, SPW, SPW, SPW, SPW, SPK, T, T, T, T, T, + T, T, T, T, SPK, SPW, SPL, SPL, SPL, SPL, SPW, SPK, T, T, T, T, + T, T, T, SPK, SPW, SPL, PRL, WHT, WHT, PRL, SPL, SPW, SPK, T, T, T, + T, T, T, SPK, SPW, PRL, WHT, WHT, WHT, WHT, PRL, SPW, SPK, T, T, T, + T, T, SPK, SPW, SPL, PRL, WHT, WHT, WHT, WHT, PRL, SPL, SPW, SPK, T, T, + T, T, SPK, SPW, SPL, PRL, WHT, WHT, WHT, WHT, PRL, SPL, SPW, SPK, T, T, + T, T, T, SPK, SPW, SPL, PRL, WHT, WHT, PRL, SPL, SPW, SPK, T, T, T, + T, T, T, SPK, SPW, SPW, SPL, SPD, SPD, SPL, SPW, SPW, SPK, T, T, T, + T, T, T, T, SPK, SPD, SPW, SPW, SPW, SPW, SPD, SPK, T, T, T, T, + T, T, T, T, SPK, SPK, SPD, SPD, SPD, SPD, SPK, SPK, T, T, T, T, + T, T, T, SPK, SPD, T, SPK, T, T, SPK, T, SPD, SPK, T, T, T, + T, T, SPK, SPD, T, T, T, T, T, T, T, T, SPD, SPK, T, T, + T, T, SPK, T, T, T, T, T, T, T, T, T, T, SPK, T, T, +}; + +/* Spawner death — cracked open, dark */ +static const uint32_t spawner_death1[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, SPK, T, T, T, T, SPK, T, T, T, T, T, + T, T, T, T, SPK, SPD, SPK, T, T, SPK, SPD, SPK, T, T, T, T, + T, T, T, SPK, SPD, SPW, SPD, SPK, SPK, SPD, SPW, SPD, SPK, T, T, T, + T, T, SPK, SPD, SPW, SPW, SPW, SPD, SPD, SPW, SPW, SPW, SPD, SPK, T, T, + T, T, SPK, SPD, SPW, SPD, SPK, BLK, BLK, SPK, SPD, SPW, SPD, SPK, T, T, + T, T, T, SPK, SPD, SPD, SPK, SPK, SPK, SPK, SPD, SPD, SPK, T, T, T, + T, T, T, T, SPK, SPK, T, T, T, T, SPK, SPK, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, +}; + +/* ── Laser turret sprites ──────────────────────────── */ +/* Laser turret: Angular metallic housing with red lens. 14x14 hitbox. + * Wall-mounted feel, darker/more angular than regular turret. */ + +#define LTG 0x707070FF /* laser turret grey */ +#define LTD 0x484848FF /* laser turret dark */ +#define LTL 0x989898FF /* laser turret light */ +#define LTK 0x303030FF /* laser turret outline */ +#define LTR 0xcc3020FF /* laser turret red lens */ +#define LRD 0x881818FF /* laser turret red dark */ +#define LRL 0xff5040FF /* laser turret red light */ + +/* Laser turret idle — angular housing, dim red lens */ +static const uint32_t laser_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, LTK, LTK, LTK, LTK, LTK, LTK, LTK, LTK, T, T, T, T, + T, T, T, LTK, LTL, LTL, LTG, LTG, LTG, LTG, LTL, LTL, LTK, T, T, T, + T, T, T, LTK, LTL, LTG, LTG, LTG, LTG, LTG, LTG, LTL, LTK, T, T, T, + T, T, T, LTK, LTG, LTG, LTD, LTD, LTD, LTD, LTG, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LTD, LRD, LTR, LTR, LRD, LTD, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LTD, LTR, LRD, LRD, LTR, LTD, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LTD, LTR, LRD, LRD, LTR, LTD, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LTD, LRD, LTR, LTR, LRD, LTD, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LTG, LTD, LTD, LTD, LTD, LTG, LTG, LTK, T, T, T, + T, T, T, LTK, LTL, LTG, LTG, LTG, LTG, LTG, LTG, LTL, LTK, T, T, T, + T, T, T, LTK, LTL, LTL, LTG, LTG, LTG, LTG, LTL, LTL, LTK, T, T, T, + T, T, T, T, LTK, LTK, LTK, LTK, LTK, LTK, LTK, LTK, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, +}; + +/* Laser turret firing — lens glows bright, ring highlight */ +static const uint32_t laser_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, LTK, LTK, LTK, LTK, LTK, LTK, LTK, LTK, T, T, T, T, + T, T, T, LTK, LTL, LTL, LTG, LTG, LTG, LTG, LTL, LTL, LTK, T, T, T, + T, T, T, LTK, LTL, LTG, LTG, LTG, LTG, LTG, LTG, LTL, LTK, T, T, T, + T, T, T, LTK, LTG, LTG, LRL, LRL, LRL, LRL, LTG, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LRL, LTR, WHT, WHT, LTR, LRL, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LRL, WHT, WHT, WHT, WHT, LRL, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LRL, WHT, WHT, WHT, WHT, LRL, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LRL, LTR, WHT, WHT, LTR, LRL, LTG, LTK, T, T, T, + T, T, T, LTK, LTG, LTG, LRL, LRL, LRL, LRL, LTG, LTG, LTK, T, T, T, + T, T, T, LTK, LTL, LTG, LTG, LTG, LTG, LTG, LTG, LTL, LTK, T, T, T, + T, T, T, LTK, LTL, LTL, LTG, LTG, LTG, LTG, LTL, LTL, LTK, T, T, T, + T, T, T, T, LTK, LTK, LTK, LTK, LTK, LTK, LTK, LTK, T, T, T, T, + T, T, T, T, T, T, T, T, 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 */ @@ -1068,10 +1295,26 @@ static const SpriteDef s_sprite_defs[] = { {6, 5, asteroid2}, {6, 6, powerup_fuel1}, {6, 7, powerup_fuel2}, + + /* Row 7: Charger */ + {7, 0, charger_idle1}, + {7, 1, charger_idle2}, + {7, 2, charger_walk1}, + {7, 3, charger_walk2}, + {7, 4, charger_walk1}, /* reuse frame */ + {7, 5, charger_walk2}, /* reuse frame */ + {7, 6, charger_death1}, + + /* Row 8: Spawner + Laser Turret */ + {8, 0, spawner_idle1}, + {8, 1, spawner_idle2}, + {8, 2, spawner_death1}, + {8, 3, laser_idle1}, + {8, 4, laser_fire1}, }; #define SHEET_COLS 8 -#define SHEET_ROWS 7 +#define SHEET_ROWS 9 SDL_Texture *sprites_generate(SDL_Renderer *renderer) { int w = SHEET_COLS * SPRITE_CELL; @@ -1278,6 +1521,42 @@ static AnimFrame s_drone_frames[] = { FRAME(1, 6, 0.2f), }; +/* Charger */ +static AnimFrame s_charger_idle_frames[] = { + FRAME(0, 7, 0.5f), + FRAME(1, 7, 0.5f), +}; + +static AnimFrame s_charger_walk_frames[] = { + FRAME(2, 7, 0.12f), + FRAME(3, 7, 0.12f), + FRAME(4, 7, 0.12f), + FRAME(5, 7, 0.12f), +}; + +static AnimFrame s_charger_death_frames[] = { + FRAME(6, 7, 0.3f), +}; + +/* Spawner */ +static AnimFrame s_spawner_idle_frames[] = { + FRAME(0, 8, 0.6f), + FRAME(1, 8, 0.6f), +}; + +static AnimFrame s_spawner_death_frames[] = { + FRAME(2, 8, 0.3f), +}; + +/* Laser Turret */ +static AnimFrame s_laser_turret_idle_frames[] = { + FRAME(3, 8, 1.0f), +}; + +static AnimFrame s_laser_turret_fire_frames[] = { + FRAME(4, 8, 0.15f), +}; + /* Exported animation definitions */ AnimDef anim_player_idle; AnimDef anim_player_run; @@ -1313,6 +1592,16 @@ AnimDef anim_asteroid; AnimDef anim_drone; +AnimDef anim_charger_idle; +AnimDef anim_charger_walk; +AnimDef anim_charger_death; + +AnimDef anim_spawner_idle; +AnimDef anim_spawner_death; + +AnimDef anim_laser_turret_idle; +AnimDef anim_laser_turret_fire; + 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}; @@ -1347,4 +1636,14 @@ void sprites_init_anims(void) { anim_asteroid = (AnimDef){s_asteroid_frames, 2, true, NULL}; anim_drone = (AnimDef){s_drone_frames, 2, true, NULL}; + + anim_charger_idle = (AnimDef){s_charger_idle_frames, 2, true, NULL}; + anim_charger_walk = (AnimDef){s_charger_walk_frames, 4, true, NULL}; + anim_charger_death = (AnimDef){s_charger_death_frames, 1, false, NULL}; + + anim_spawner_idle = (AnimDef){s_spawner_idle_frames, 2, true, NULL}; + anim_spawner_death = (AnimDef){s_spawner_death_frames, 1, false, NULL}; + + anim_laser_turret_idle = (AnimDef){s_laser_turret_idle_frames, 1, true, NULL}; + anim_laser_turret_fire = (AnimDef){s_laser_turret_fire_frames, 1, false, NULL}; } diff --git a/src/game/sprites.h b/src/game/sprites.h index b9d0455..d08bf78 100644 --- a/src/game/sprites.h +++ b/src/game/sprites.h @@ -8,10 +8,15 @@ * Each sprite cell is 16x16 pixels. * The sheet is organized in rows: * - * Row 0: Player idle (2 frames), run (4 frames), jump (1), fall (1) - * Row 1: Grunt idle (2 frames), walk (4 frames), death (2 frames) - * Row 2: Flyer idle (2 frames), fly (4 frames), death (2 frames) - * Row 3: Projectiles: bullet (2 frames), impact (3 frames), enemy bullet (2) + * Row 0: Player idle (2), run (4), jump (1), fall (1) + * Row 1: Grunt idle (2), walk (4), death (1) + * Row 2: Flyer idle (2), fly (4), death (1) + * Row 3: Projectiles: bullet (2), impact (3), enemy bullet (2) + * Row 4: Turret (2), platform (1), flame vent (3), force field (2) + * Row 5: Force field off (1), powerups: health (2), jetpack (2), drone (2) + * Row 6: Drone companion (2), gun powerup (2), asteroid (2), fuel (2) + * Row 7: Charger idle (2), walk (4), death (1) + * Row 8: Spawner idle (2), death (1), laser turret idle (1), fire (1) */ #define SPRITE_CELL 16 @@ -62,6 +67,19 @@ extern AnimDef anim_asteroid; /* ── Drone animation ───────────────────────────── */ extern AnimDef anim_drone; +/* ── Charger animations ────────────────────────── */ +extern AnimDef anim_charger_idle; +extern AnimDef anim_charger_walk; +extern AnimDef anim_charger_death; + +/* ── Spawner animations ────────────────────────── */ +extern AnimDef anim_spawner_idle; +extern AnimDef anim_spawner_death; + +/* ── Laser turret animations ───────────────────── */ +extern AnimDef anim_laser_turret_idle; +extern AnimDef anim_laser_turret_fire; + /* Initialize all animation definitions */ void sprites_init_anims(void);