#include "kknd.h" /* ========================================================================= * projectiles.c - Bullets, missiles and particle effects. * ========================================================================= */ static RNG g_prng; static int g_prng_init = 0; void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld, float dmg, Faction f, int kind) { if (g->bullet_count >= MAX_BULLETS) return; Bullet *b = &g->bullets[g->bullet_count++]; b->x = x; b->y = y; b->target_unit = target_unit; b->target_bld = target_bld; b->damage = dmg; b->faction = f; b->kind = kind; b->speed = (kind == 3) ? 320 : (kind == 1 ? 420 : 520); b->life = 3.0f; if (target_unit >= 0) { Unit *t = unit_by_id(g, target_unit); if (t) { b->tx = t->x; b->ty = t->y; } } else if (target_bld >= 0) { Building *t = bld_by_id(g, target_bld); if (t) { b->tx = (float)(t->tx*TILE + t->w*TILE/2); b->ty = (float)(t->ty*TILE + t->h*TILE/2); } } } void bullet_update(Game *g, float dt) { for (int i = g->bullet_count - 1; i >= 0; i--) { Bullet *b = &g->bullets[i]; b->life -= dt; /* home onto target if still alive */ if (b->target_unit >= 0) { Unit *t = unit_by_id(g, b->target_unit); if (t) { b->tx = t->x; b->ty = t->y; } else b->target_unit = -1; } else if (b->target_bld >= 0) { Building *t = bld_by_id(g, b->target_bld); if (t) { b->tx = (float)(t->tx*TILE+t->w*TILE/2); b->ty = (float)(t->ty*TILE+t->h*TILE/2); } else b->target_bld = -1; } float dx = b->tx - b->x, dy = b->ty - b->y; float d = (float)sqrtf(dx*dx + dy*dy); float step = b->speed * dt; if (d <= step + 4 || b->life <= 0) { /* impact */ if (b->target_unit >= 0) { Unit *t = unit_by_id(g, b->target_unit); if (t) unit_apply_damage(g, t, b->damage); } else if (b->target_bld >= 0) { Building *t = bld_by_id(g, b->target_bld); if (t) bld_apply_damage(g, t, b->damage); } /* denser impact: fire/laser core + smoke ring + debris */ int hit_kind = (b->kind == 2) ? 2 : 1; particle_burst(g, b->tx, b->ty, 12, hit_kind, 255, 200, 80); particle_burst(g, b->tx, b->ty, 8, 0, 90, 80, 60); particle_burst(g, b->tx, b->ty, 5, 1, 255, 120, 35); if (b->kind == 1 || b->kind == 3) particle_burst(g, b->tx, b->ty, 6, 3, 255, 140, 40); /* swap-remove */ g->bullets[i] = g->bullets[g->bullet_count - 1]; g->bullet_count--; continue; } b->x += dx/d * step; b->y += dy/d * step; /* trail — denser muzzle streak for non-ballistic shells */ if (b->kind != 0) particle_spawn(g, b->x, b->y, 2, 255, 180, 60); if (b->kind == 1 || b->kind == 3) { particle_spawn(g, b->x - dx / d * 3.0f, b->y - dy / d * 3.0f, 3, 255, 160, 50); particle_spawn(g, b->x - dx / d * 6.0f, b->y - dy / d * 6.0f, 1, 255, 140, 40); } if (b->kind == 2) particle_spawn(g, b->x, b->y, 2, 180, 120, 255); } } void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, uint8_t cb) { if (g->particle_count >= MAX_PARTICLES) return; if (!g_prng_init) { rng_seed(&g_prng, 0x1234ABCDULL); g_prng_init = 1; } Particle *p = &g->particles[g->particle_count++]; p->x = x; p->y = y; float ang = rng_frange(&g_prng, 0, 6.283f); float sp = rng_frange(&g_prng, 10, 60); p->vx = (float)cosf(ang) * sp; p->vy = (float)sinf(ang) * sp; /* lingering columns: smoke / fire / spark|debris (KKND2 clash plate) */ if (kind == 0) p->life = p->max_life = 3.6f; else if (kind == 1) p->life = p->max_life = 2.6f; else p->life = p->max_life = 1.4f; p->r = cr; p->g = cg; p->b = cb; p->kind = kind; } void particle_burst(Game *g, float x, float y, int n, int kind, uint8_t cr, uint8_t cg, uint8_t cb) { for (int i = 0; i < n; i++) particle_spawn(g, x, y, kind, cr, cg, cb); } void particle_update(Game *g, float dt) { for (int i = g->particle_count - 1; i >= 0; i--) { Particle *p = &g->particles[i]; p->life -= dt; p->x += p->vx * dt; p->y += p->vy * dt; p->vx *= 0.92f; p->vy *= 0.92f; /* screen-y grows downward: subtract to rise (fire + smoke columns) */ if (p->kind == 1) p->vy -= 42 * dt; if (p->kind == 0) p->vy -= 22 * dt; if (p->life <= 0) { g->particles[i] = g->particles[g->particle_count - 1]; g->particle_count--; } } }