diff --git a/src/engine/particle.c b/src/engine/particle.c index c195502..18f8ba6 100644 --- a/src/engine/particle.c +++ b/src/engine/particle.c @@ -530,6 +530,124 @@ void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir) { particle_emit(&dust); } +void particle_emit_hit_sparks(Vec2 pos) { + /* Bright orange-white sparks — fast, short-lived, spray outward */ + ParticleBurst sparks = { + .origin = pos, + .count = 8, + .speed_min = 60.0f, + .speed_max = 180.0f, + .life_min = 0.08f, + .life_max = 0.2f, + .size_min = 1.0f, + .size_max = 2.0f, + .spread = (float)M_PI, + .direction = 0, + .drag = 3.0f, + .gravity_scale = 0.4f, + .color = {255, 200, 100, 255}, /* orange-white */ + .color_vary = true, + }; + particle_emit(&sparks); + + /* Brief white flash at impact point */ + ParticleBurst flash = { + .origin = pos, + .count = 3, + .speed_min = 5.0f, + .speed_max = 15.0f, + .life_min = 0.03f, + .life_max = 0.06f, + .size_min = 2.0f, + .size_max = 3.5f, + .spread = (float)M_PI, + .direction = 0, + .drag = 8.0f, + .gravity_scale = 0.0f, + .color = {255, 255, 230, 255}, /* bright white */ + .color_vary = false, + }; + particle_emit(&flash); +} + +void particle_emit_metal_explosion(Vec2 pos) { + /* Metal shrapnel — fast grey/silver chunks flying outward */ + ParticleBurst shrapnel = { + .origin = pos, + .count = 16, + .speed_min = 60.0f, + .speed_max = 200.0f, + .life_min = 0.2f, + .life_max = 0.6f, + .size_min = 1.5f, + .size_max = 3.5f, + .spread = (float)M_PI, + .direction = 0, + .drag = 2.0f, + .gravity_scale = 0.5f, + .color = {180, 180, 190, 255}, /* silver-grey */ + .color_vary = true, + }; + particle_emit(&shrapnel); + + /* Hot orange sparks — electrical/mechanical innards */ + ParticleBurst sparks = { + .origin = pos, + .count = 10, + .speed_min = 40.0f, + .speed_max = 150.0f, + .life_min = 0.15f, + .life_max = 0.35f, + .size_min = 1.0f, + .size_max = 2.5f, + .spread = (float)M_PI, + .direction = 0, + .drag = 2.5f, + .gravity_scale = 0.3f, + .color = {255, 160, 40, 255}, /* hot orange */ + .color_vary = true, + }; + particle_emit(&sparks); + + /* Bright white flash at center — brief pop */ + ParticleBurst flash = { + .origin = pos, + .count = 5, + .speed_min = 5.0f, + .speed_max = 20.0f, + .life_min = 0.04f, + .life_max = 0.08f, + .size_min = 3.0f, + .size_max = 5.0f, + .spread = (float)M_PI, + .direction = 0, + .drag = 8.0f, + .gravity_scale = 0.0f, + .color = {255, 240, 200, 255}, /* bright flash */ + .color_vary = false, + }; + particle_emit(&flash); + + /* Smoke cloud — slower, lingers */ + ParticleBurst smoke = { + .origin = pos, + .count = 8, + .speed_min = 15.0f, + .speed_max = 50.0f, + .life_min = 0.3f, + .life_max = 0.7f, + .size_min = 2.5f, + .size_max = 4.5f, + .spread = (float)M_PI, + .direction = 0, + .drag = 3.5f, + .gravity_scale = -0.1f, /* floats up */ + .color = {120, 120, 130, 180}, /* dark smoke */ + .color_vary = true, + }; + particle_emit(&smoke); +} + /* Spawn a single dust mote with the given visual properties. */ static void spawn_dust_mote(Vec2 pos, Vec2 vel, float life_min, float life_max, diff --git a/src/engine/particle.h b/src/engine/particle.h index b39d4d5..a174c17 100644 --- a/src/engine/particle.h +++ b/src/engine/particle.h @@ -89,6 +89,12 @@ void particle_emit_muzzle_flash(Vec2 pos, Vec2 shoot_dir); /* Wall slide dust (small puffs while scraping against a wall) */ void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir); +/* Metal hit sparks (turret/machine takes non-lethal damage) */ +void particle_emit_hit_sparks(Vec2 pos); + +/* Metal explosion (turret/machine death — shrapnel + flash) */ +void particle_emit_metal_explosion(Vec2 pos); + /* Ambient atmosphere dust (call each frame for Mars Surface levels). * Spawns subtle dust motes around the camera viewport that drift with wind. * cam_pos = camera top-left world position, vp = viewport size in pixels. */ diff --git a/src/game/level.c b/src/game/level.c index 9fb74a7..61eb71a 100644 --- a/src/game/level.c +++ b/src/game/level.c @@ -177,36 +177,45 @@ static Camera *s_active_camera = NULL; static void damage_entity(Entity *target, int damage) { target->health -= damage; + + Vec2 center = vec2( + target->body.pos.x + target->body.size.x * 0.5f, + target->body.pos.y + target->body.size.y * 0.5f + ); + if (target->health <= 0) { target->flags |= ENTITY_DEAD; - /* Death particles — centered on entity */ - Vec2 center = vec2( - target->body.pos.x + target->body.size.x * 0.5f, - target->body.pos.y + target->body.size.y * 0.5f - ); - SDL_Color death_color; - if (target->type == ENT_ENEMY_GRUNT) { - death_color = (SDL_Color){200, 60, 60, 255}; /* red debris */ - } else if (target->type == ENT_ENEMY_FLYER) { - death_color = (SDL_Color){140, 80, 200, 255}; /* purple puff */ - } else if (target->type == ENT_TURRET || target->type == ENT_LASER_TURRET) { - death_color = (SDL_Color){160, 160, 160, 255}; /* metal scraps */ - } else if (target->type == ENT_ENEMY_CHARGER) { - death_color = (SDL_Color){220, 140, 40, 255}; /* orange spark */ - } else if (target->type == ENT_SPAWNER) { - death_color = (SDL_Color){180, 60, 180, 255}; /* purple burst */ + /* Death particles — turrets get a metal explosion, others a puff */ + if (target->type == ENT_TURRET || target->type == ENT_LASER_TURRET) { + particle_emit_metal_explosion(center); } else { - death_color = (SDL_Color){200, 200, 200, 255}; /* grey */ + SDL_Color death_color; + if (target->type == ENT_ENEMY_GRUNT) { + death_color = (SDL_Color){200, 60, 60, 255}; + } else if (target->type == ENT_ENEMY_FLYER) { + death_color = (SDL_Color){140, 80, 200, 255}; + } else if (target->type == ENT_ENEMY_CHARGER) { + death_color = (SDL_Color){220, 140, 40, 255}; + } else if (target->type == ENT_SPAWNER) { + death_color = (SDL_Color){180, 60, 180, 255}; + } else { + death_color = (SDL_Color){200, 200, 200, 255}; + } + particle_emit_death_puff(center, death_color); } - particle_emit_death_puff(center, death_color); - /* Screen shake on kill */ + /* Screen shake on kill — stronger for turret explosions */ if (s_active_camera) { - camera_shake(s_active_camera, 2.0f, 0.15f); + float intensity = (target->type == ENT_TURRET || + target->type == ENT_LASER_TURRET) ? 3.5f : 2.0f; + camera_shake(s_active_camera, intensity, 0.15f); } audio_play_sound_at(s_sfx_enemy_death, 80, center, 0); + } else if (target->type == ENT_TURRET || target->type == ENT_LASER_TURRET) { + /* Hit marker sparks on non-lethal turret damage */ + particle_emit_hit_sparks(center); } }