187 lines
7.3 KiB
C
187 lines
7.3 KiB
C
#include "kknd.h"
|
|
|
|
/* =========================================================================
|
|
* projectiles.c - Bullets, missiles and particle effects.
|
|
* ========================================================================= */
|
|
|
|
static RNG g_prng;
|
|
static int g_prng_init = 0;
|
|
|
|
/* Place bullet on chord+parabola: loft peaks at prog=0.5 (screen-y up = -). */
|
|
static void bullet_place_arc(Bullet *b) {
|
|
float t = b->prog;
|
|
if (t < 0.0f) t = 0.0f;
|
|
if (t > 1.0f) t = 1.0f;
|
|
b->x = b->ox + (b->tx - b->ox) * t;
|
|
b->y = b->oy + (b->ty - b->oy) * t - b->loft * 4.0f * t * (1.0f - t);
|
|
}
|
|
|
|
/* Mid-flight place: set prog then arc (shell/missile) or linear chord. */
|
|
void bullet_place_at_prog(Bullet *b, float prog) {
|
|
if (prog < 0.0f) prog = 0.0f;
|
|
if (prog > 1.0f) prog = 1.0f;
|
|
b->prog = prog;
|
|
if ((b->kind == 1 || b->kind == 3) && b->loft > 0.5f)
|
|
bullet_place_arc(b);
|
|
else {
|
|
b->x = b->ox + (b->tx - b->ox) * prog;
|
|
b->y = b->oy + (b->ty - b->oy) * prog;
|
|
}
|
|
}
|
|
|
|
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->ox = x; b->oy = y;
|
|
b->prog = 0.0f;
|
|
b->loft = 0.0f;
|
|
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;
|
|
b->tx = x; b->ty = y;
|
|
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); }
|
|
}
|
|
/* shells / missiles get a parabolic loft scaled by range */
|
|
if (kind == 1 || kind == 3) {
|
|
float dx = b->tx - b->ox, dy = b->ty - b->oy;
|
|
float dist = sqrtf(dx * dx + dy * dy);
|
|
float base = (kind == 3) ? 28.0f : 18.0f;
|
|
b->loft = base + dist * ((kind == 3) ? 0.22f : 0.14f);
|
|
if (b->loft > 90.0f) b->loft = 90.0f;
|
|
bullet_place_arc(b);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int lofted = (b->kind == 1 || b->kind == 3) && b->loft > 0.5f;
|
|
float hit = 0;
|
|
if (lofted) {
|
|
float cdx = b->tx - b->ox, cdy = b->ty - b->oy;
|
|
float chord = sqrtf(cdx * cdx + cdy * cdy);
|
|
if (chord < 1.0f) chord = 1.0f;
|
|
b->prog += (b->speed * dt) / chord;
|
|
bullet_place_arc(b);
|
|
hit = (b->prog >= 1.0f || b->life <= 0) ? 1.0f : 0.0f;
|
|
} else {
|
|
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) hit = 1.0f;
|
|
else {
|
|
b->x += dx/d * step;
|
|
b->y += dy/d * step;
|
|
}
|
|
}
|
|
if (hit > 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);
|
|
}
|
|
/* bigger KKND2 impact bloom: fire core + smoke ring + debris + flash */
|
|
int hit_kind = (b->kind == 2) ? 2 : 1;
|
|
int shell = (b->kind == 1 || b->kind == 3);
|
|
particle_burst(g, b->tx, b->ty, shell ? 22 : 14, hit_kind, 255, 200, 80);
|
|
particle_burst(g, b->tx, b->ty, shell ? 16 : 10, 0, 90, 80, 60);
|
|
particle_burst(g, b->tx, b->ty, shell ? 12 : 6, 1, 255, 120, 35);
|
|
particle_burst(g, b->tx + 4, b->ty - 3, shell ? 10 : 5, 1, 255, 170, 50);
|
|
particle_burst(g, b->tx - 5, b->ty + 4, shell ? 8 : 4, 2, 255, 230, 100);
|
|
if (shell) {
|
|
particle_burst(g, b->tx, b->ty, 14, 3, 255, 140, 40);
|
|
particle_burst(g, b->tx + 8, b->ty + 2, 8, 3, 90, 75, 55);
|
|
particle_burst(g, b->tx - 6, b->ty - 5, 8, 0, 70, 60, 50);
|
|
}
|
|
/* swap-remove */
|
|
g->bullets[i] = g->bullets[g->bullet_count - 1];
|
|
g->bullet_count--;
|
|
continue;
|
|
}
|
|
/* trail — denser muzzle streak for non-MG */
|
|
if (b->kind != 0)
|
|
particle_spawn(g, b->x, b->y, 2, 255, 180, 60);
|
|
if (b->kind == 1 || b->kind == 3) {
|
|
float tback = b->prog - 0.04f;
|
|
if (tback < 0.0f) tback = 0.0f;
|
|
float bx = b->ox + (b->tx - b->ox) * tback;
|
|
float by = b->oy + (b->ty - b->oy) * tback
|
|
- b->loft * 4.0f * tback * (1.0f - tback);
|
|
particle_spawn(g, bx, by, 3, 255, 160, 50);
|
|
tback = b->prog - 0.08f;
|
|
if (tback < 0.0f) tback = 0.0f;
|
|
bx = b->ox + (b->tx - b->ox) * tback;
|
|
by = b->oy + (b->ty - b->oy) * tback
|
|
- b->loft * 4.0f * tback * (1.0f - tback);
|
|
particle_spawn(g, bx, by, 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--;
|
|
}
|
|
}
|
|
}
|