From b4d33887016bc088d22d56fe73cfbed1eb31bee8 Mon Sep 17 00:00:00 2001 From: "local-model/engine-L2-grok-4.5" Date: Sat, 19 Sep 2026 17:42:57 +0200 Subject: [PATCH] engine-c: denser mid-air tracers + artillery/missile arcs --- engine-c/include/kknd.h | 4 ++ engine-c/src/engine.c | 8 ++++ engine-c/src/main.c | 60 +++++++++++++++++------------ engine-c/src/projectiles.c | 79 ++++++++++++++++++++++++++++++++------ engine-c/src/render.c | 57 +++++++++++++++++---------- engine-c/src/snapshot.c | 51 +++++++++++++++--------- engine-c/src/units.c | 4 +- 7 files changed, 187 insertions(+), 76 deletions(-) diff --git a/engine-c/include/kknd.h b/engine-c/include/kknd.h index de679a7..ad8bc38 100644 --- a/engine-c/include/kknd.h +++ b/engine-c/include/kknd.h @@ -312,6 +312,9 @@ typedef struct Building { /* ----------------------------------------------------------------------- */ typedef struct { float x, y, tx, ty; + float ox, oy; /* origin for ballistic arc (shell/missile) */ + float loft; /* peak height above chord; 0 = linear */ + float prog; /* 0..1 along chord for lofted kinds */ float speed; float damage; Faction faction; @@ -560,6 +563,7 @@ int bld_research(Game *g, Faction f, int up); /* ----------------------------------------------------------------------- */ void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld, float dmg, Faction f, int kind); +void bullet_place_at_prog(Bullet *b, float prog); void bullet_update(Game *g, float dt); void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, uint8_t cb); void particle_burst(Game *g, float x, float y, int n, int kind, uint8_t cr, uint8_t cg, uint8_t cb); diff --git a/engine-c/src/engine.c b/engine-c/src/engine.c index 4edb456..b75a9dc 100644 --- a/engine-c/src/engine.c +++ b/engine-c/src/engine.c @@ -210,10 +210,14 @@ static void place_showcase(Game *g, Faction human, Faction enemy) { unit_spawn(g, UT_RAVAGER, human, wx + 78, wy - 58); unit_spawn(g, UT_ARTILLERY, human, wx + 18, wy + 48); unit_spawn(g, UT_ARTILLERY, human, wx + 38, wy + 62); + unit_spawn(g, UT_ARTILLERY, human, wx + 8, wy + 28); + unit_spawn(g, UT_ARTILLERY, human, wx + 52, wy + 44); unit_spawn(g, UT_FLAME, human, wx - 40, wy + 12); unit_spawn(g, UT_FLAME, human, wx + 105, wy - 5); unit_spawn(g, UT_MISSILE, human, wx - 55, wy - 8); unit_spawn(g, UT_MISSILE, human, wx + 110, wy - 40); + unit_spawn(g, UT_MISSILE, human, wx + 96, wy - 52); + unit_spawn(g, UT_MISSILE, human, wx + 120, wy - 18); unit_spawn(g, UT_JEEP, human, wx + 32, wy - 52); unit_spawn(g, UT_JEEP, human, wx + 48, wy - 68); unit_spawn(g, UT_BUGGY, human, wx + 55, wy - 78); @@ -649,8 +653,12 @@ static void place_showcase(Game *g, Faction human, Faction enemy) { raid[rn++] = unit_spawn(g, UT_MISSILE, enemy, rx + 62, ry - 28); raid[rn++] = unit_spawn(g, UT_MISSILE, enemy, rx + 78, ry - 8); raid[rn++] = unit_spawn(g, UT_MISSILE, enemy, rx + 94, ry + 6); + raid[rn++] = unit_spawn(g, UT_MISSILE, enemy, rx + 108, ry - 20); + raid[rn++] = unit_spawn(g, UT_MISSILE, enemy, rx + 70, ry + 14); raid[rn++] = unit_spawn(g, UT_ARTILLERY, enemy, rx + 85, ry + 10); raid[rn++] = unit_spawn(g, UT_ARTILLERY, enemy, rx + 98, ry - 18); + raid[rn++] = unit_spawn(g, UT_ARTILLERY, enemy, rx + 112, ry + 4); + raid[rn++] = unit_spawn(g, UT_ARTILLERY, enemy, rx + 76, ry - 40); for (int i = 0; i < 8; i++) raid[rn++] = unit_spawn(g, UT_INFANTRY, enemy, rx - 28 + (i % 4) * 14, ry - 8 + (i / 4) * 14); diff --git a/engine-c/src/main.c b/engine-c/src/main.c index 6b8e304..35b6f50 100644 --- a/engine-c/src/main.c +++ b/engine-c/src/main.c @@ -203,36 +203,39 @@ int main(int argc, char **argv) { } } /* Mid-flight shells/missiles/lasers: after ~1s sim most live - * bullets have already impacted. Reseed between nearby opposing - * pairs so the PNG shows KKND2-like tracers across the clash. */ + * bullets have already impacted. Reseed from attacker origin with + * mid-flight prog so lofted kinds keep their parabolic arc in PNG. */ { int spawned = 0; const float pair_r2 = 180.0f * 180.0f; - for (int i = 0; i < g->unit_count && spawned < 72; i++) { + for (int i = 0; i < g->unit_count && spawned < 110; i++) { Unit *a = &g->units[i]; if (a->dead) continue; float ax = a->x - clash_x, ay = a->y - clash_y; if (ax * ax + ay * ay > r2) continue; int shots = 0; - for (int j = 0; j < g->unit_count && spawned < 72 && shots < 3; j++) { + for (int j = 0; j < g->unit_count && spawned < 110 && shots < 4; j++) { Unit *b = &g->units[j]; if (b->dead || b->faction == a->faction) continue; float dx = b->x - a->x, dy = b->y - a->y; float d2 = dx * dx + dy * dy; if (d2 > pair_r2 || d2 < 20.0f * 20.0f) continue; - int kind = (spawned % 4 == 0) ? 3 - : (spawned % 4 == 1) ? 1 - : (spawned % 4 == 2) ? 2 : 0; + /* bias shell/missile arcs over MG/laser */ + int kind = (spawned % 5 == 0) ? 0 + : (spawned % 5 == 1) ? 2 + : (spawned % 5 == 2) ? 1 + : (spawned % 5 == 3) ? 3 : 1; float t = 0.18f + 0.60f * ((spawned * 37) % 100) / 100.0f; - float sx = a->x + dx * t; - float sy = a->y + dy * t; - bullet_spawn(g, sx, sy, b->id, -1, 1.0f, a->faction, kind); + int before = g->bullet_count; + bullet_spawn(g, a->x, a->y, b->id, -1, 1.0f, a->faction, kind); + if (g->bullet_count > before) + bullet_place_at_prog(&g->bullets[g->bullet_count - 1], t); spawned++; shots++; } } /* turret / base guns → nearest enemy in clash */ - for (int i = 0; i < g->bld_count && spawned < 88; i++) { + for (int i = 0; i < g->bld_count && spawned < 130; i++) { Building *bl = &g->buildings[i]; if (bl->dead || (bl->type != BT_TURRET && bl->type != BT_BASE)) continue; @@ -250,10 +253,12 @@ int main(int argc, char **argv) { } if (best < 0) continue; Unit *tgt = &g->units[best]; - float dx = tgt->x - bx, dy = tgt->y - by; float t = 0.25f + 0.45f * ((spawned * 19) % 100) / 100.0f; - bullet_spawn(g, bx + dx * t, by + dy * t, tgt->id, -1, - 2.0f, bl->faction, (spawned & 1) ? 1 : 2); + int kind = (spawned % 3 == 0) ? 3 : 1; + int before = g->bullet_count; + bullet_spawn(g, bx, by, tgt->id, -1, 2.0f, bl->faction, kind); + if (g->bullet_count > before) + bullet_place_at_prog(&g->bullets[g->bullet_count - 1], t); particle_burst(g, bx, by - 4.0f, 5, 2, 255, 220, 100); spawned++; } @@ -307,29 +312,32 @@ int main(int argc, char **argv) { int spawned2 = 0; const float pair_r2 = 160.0f * 160.0f; const float r2b = 140.0f * 140.0f; - for (int i = 0; i < g->unit_count && spawned2 < 48; i++) { + for (int i = 0; i < g->unit_count && spawned2 < 72; i++) { Unit *a = &g->units[i]; if (a->dead) continue; float ax = a->x - clash2_x, ay = a->y - clash2_y; if (ax * ax + ay * ay > r2b) continue; int shots = 0; - for (int j = 0; j < g->unit_count && spawned2 < 48 && shots < 3; j++) { + for (int j = 0; j < g->unit_count && spawned2 < 72 && shots < 4; j++) { Unit *b = &g->units[j]; if (b->dead || b->faction == a->faction) continue; float dx = b->x - a->x, dy = b->y - a->y; float d2 = dx * dx + dy * dy; if (d2 > pair_r2 || d2 < 18.0f * 18.0f) continue; - int kind = (spawned2 % 4 == 0) ? 3 - : (spawned2 % 4 == 1) ? 1 - : (spawned2 % 4 == 2) ? 2 : 0; + int kind = (spawned2 % 5 == 0) ? 0 + : (spawned2 % 5 == 1) ? 2 + : (spawned2 % 5 == 2) ? 1 + : (spawned2 % 5 == 3) ? 3 : 1; float t = 0.18f + 0.58f * ((spawned2 * 41) % 100) / 100.0f; - bullet_spawn(g, a->x + dx * t, a->y + dy * t, - b->id, -1, 1.0f, a->faction, kind); + int before = g->bullet_count; + bullet_spawn(g, a->x, a->y, b->id, -1, 1.0f, a->faction, kind); + if (g->bullet_count > before) + bullet_place_at_prog(&g->bullets[g->bullet_count - 1], t); spawned2++; shots++; } } - for (int i = 0; i < g->bld_count && spawned2 < 56; i++) { + for (int i = 0; i < g->bld_count && spawned2 < 88; i++) { Building *bl = &g->buildings[i]; if (bl->dead || (bl->type != BT_TURRET && bl->type != BT_BASE)) continue; @@ -347,10 +355,12 @@ int main(int argc, char **argv) { } if (best < 0) continue; Unit *tgt = &g->units[best]; - float dx = tgt->x - bx, dy = tgt->y - by; float t = 0.26f + 0.44f * ((spawned2 * 23) % 100) / 100.0f; - bullet_spawn(g, bx + dx * t, by + dy * t, tgt->id, -1, - 2.0f, bl->faction, (spawned2 & 1) ? 1 : 2); + int kind = (spawned2 % 3 == 0) ? 3 : 1; + int before = g->bullet_count; + bullet_spawn(g, bx, by, tgt->id, -1, 2.0f, bl->faction, kind); + if (g->bullet_count > before) + bullet_place_at_prog(&g->bullets[g->bullet_count - 1], t); spawned2++; } } diff --git a/engine-c/src/projectiles.c b/engine-c/src/projectiles.c index bf41d54..74d0a38 100644 --- a/engine-c/src/projectiles.c +++ b/engine-c/src/projectiles.c @@ -7,11 +7,36 @@ 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; @@ -19,6 +44,7 @@ void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld, 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; } @@ -27,6 +53,15 @@ void bullet_spawn(Game *g, float x, float y, int target_unit, int 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) { @@ -45,10 +80,26 @@ void bullet_update(Game *g, float dt) { 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) { + 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); @@ -69,16 +120,22 @@ void bullet_update(Game *g, float dt) { g->bullet_count--; continue; } - b->x += dx/d * step; - b->y += dy/d * step; - /* trail — denser muzzle streak for non-ballistic shells */ + /* 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) { - 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); + 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); diff --git a/engine-c/src/render.c b/engine-c/src/render.c index 83d8736..6679217 100644 --- a/engine-c/src/render.c +++ b/engine-c/src/render.c @@ -439,36 +439,53 @@ void render_units(Game *g) { } void render_bullets(Game *g) { - /* Kind-aware tracers / shells with streak tails (parity with snapshot.c). */ + /* Kind-aware tracers / shells; lofted kinds sample the parabola trail. */ for (int i = 0; i < g->bullet_count; i++) { Bullet *b = &g->bullets[i]; int sx = (int)(b->x - g->cam.x), sy = (int)(b->y - g->cam.y); - float dx = b->tx - b->x, dy = b->ty - b->y; - float d = sqrtf(dx * dx + dy * dy); - if (d > 1.0f) { - float ux = dx / d, uy = dy / d; - int len = (b->kind == 2) ? 32 : (b->kind == 3) ? 24 - : (b->kind == 1) ? 20 : 14; + int lofted = (b->kind == 1 || b->kind == 3) && b->loft > 0.5f; + if (lofted) { + int len = (b->kind == 3) ? 28 : 22; + float step = 0.018f; for (int t = 1; t <= len; t++) { - int tx = sx - (int)(ux * (float)t * 0.75f); - int ty = sy - (int)(uy * (float)t * 0.75f); - Uint8 fade = (Uint8)(255 - t * 9); - if (b->kind == 2) { - /* laser: cyan→magenta streak */ - SDL_SetRenderDrawColor(g->ren, fade, 80, 255, 220); - SDL_RenderFillRect(g->ren, &(SDL_Rect){ tx - 1, ty - 1, - (t < 3) ? 3 : 2, (t < 3) ? 3 : 2 }); - SDL_SetRenderDrawColor(g->ren, 220, 180, 255, 255); - SDL_RenderDrawPoint(g->ren, tx, ty); - } else if (b->kind == 3) { - SDL_SetRenderDrawColor(g->ren, 255, (Uint8)(120 - t * 4), 30, 230); + float tp = b->prog - step * (float)t; + if (tp < 0.0f) break; + float wx = b->ox + (b->tx - b->ox) * tp; + float wy = b->oy + (b->ty - b->oy) * tp + - b->loft * 4.0f * tp * (1.0f - tp); + int tx = (int)(wx - g->cam.x); + int ty = (int)(wy - g->cam.y); + if (b->kind == 3) { + SDL_SetRenderDrawColor(g->ren, 255, (Uint8)(120 - t * 3), 30, 230); SDL_RenderFillRect(g->ren, &(SDL_Rect){ tx - 1, ty - 1, (t < 4) ? 3 : 2, (t < 4) ? 3 : 2 }); } else { - SDL_SetRenderDrawColor(g->ren, 255, (Uint8)(200 - t * 8), 50, 220); + SDL_SetRenderDrawColor(g->ren, 255, (Uint8)(200 - t * 7), 50, 220); SDL_RenderFillRect(g->ren, &(SDL_Rect){ tx, ty, 2, 2 }); } } + } else { + float dx = b->tx - b->x, dy = b->ty - b->y; + float d = sqrtf(dx * dx + dy * dy); + if (d > 1.0f) { + float ux = dx / d, uy = dy / d; + int len = (b->kind == 2) ? 32 : 14; + for (int t = 1; t <= len; t++) { + int tx = sx - (int)(ux * (float)t * 0.75f); + int ty = sy - (int)(uy * (float)t * 0.75f); + Uint8 fade = (Uint8)(255 - t * 9); + if (b->kind == 2) { + SDL_SetRenderDrawColor(g->ren, fade, 80, 255, 220); + SDL_RenderFillRect(g->ren, &(SDL_Rect){ tx - 1, ty - 1, + (t < 3) ? 3 : 2, (t < 3) ? 3 : 2 }); + SDL_SetRenderDrawColor(g->ren, 220, 180, 255, 255); + SDL_RenderDrawPoint(g->ren, tx, ty); + } else { + SDL_SetRenderDrawColor(g->ren, 255, (Uint8)(200 - t * 8), 50, 220); + SDL_RenderFillRect(g->ren, &(SDL_Rect){ tx, ty, 2, 2 }); + } + } + } } int rad = (b->kind == 1 || b->kind == 3) ? 8 : 5; SDL_SetRenderDrawColor(g->ren, 255, 160, 40, 255); diff --git a/engine-c/src/snapshot.c b/engine-c/src/snapshot.c index 2f34ab9..fcb7b49 100644 --- a/engine-c/src/snapshot.c +++ b/engine-c/src/snapshot.c @@ -925,28 +925,43 @@ void snapshot_capture(Game *g, const char *path) { draw_unit(u, sx, sy); } - /* projectiles — bright tracers / shells with streak tails (KKND2-like) */ + /* projectiles — lofted shell/missile sample parabola; else linear streak */ for (int i = 0; i < g->bullet_count; i++) { Bullet *p = &g->bullets[i]; int sx = (int)(p->x - camx), sy = (int)(p->y - camy); - float dx = p->tx - p->x, dy = p->ty - p->y; - float d = sqrtf(dx * dx + dy * dy); - if (d > 1.0f) { - float ux = dx / d, uy = dy / d; - int len = (p->kind == 2) ? 32 : (p->kind == 3) ? 24 - : (p->kind == 1) ? 20 : 14; + int lofted = (p->kind == 1 || p->kind == 3) && p->loft > 0.5f; + if (lofted) { + int len = (p->kind == 3) ? 28 : 22; + float step = 0.018f; for (int t = 1; t <= len; t++) { - int tx = sx - (int)(ux * (float)t * 0.75f); - int ty = sy - (int)(uy * (float)t * 0.75f); - uint8_t fade = (uint8_t)(255 - t * 9); - if (p->kind == 2) { - /* laser beam: cyan→magenta streak */ - disc(tx, ty, (t < 3) ? 2 : 1, fade, 80, 255); - px(tx, ty, 220, 180, 255); - } else if (p->kind == 3) { - disc(tx, ty, (t < 4) ? 2 : 1, 255, (uint8_t)(120 - t * 4), 30); - } else { - disc(tx, ty, 1, 255, (uint8_t)(200 - t * 8), 50); + float tp = p->prog - step * (float)t; + if (tp < 0.0f) break; + float wx = p->ox + (p->tx - p->ox) * tp; + float wy = p->oy + (p->ty - p->oy) * tp + - p->loft * 4.0f * tp * (1.0f - tp); + int tx = (int)(wx - camx); + int ty = (int)(wy - camy); + if (p->kind == 3) + disc(tx, ty, (t < 4) ? 2 : 1, 255, (uint8_t)(120 - t * 3), 30); + else + disc(tx, ty, 1, 255, (uint8_t)(200 - t * 7), 50); + } + } else { + float dx = p->tx - p->x, dy = p->ty - p->y; + float d = sqrtf(dx * dx + dy * dy); + if (d > 1.0f) { + float ux = dx / d, uy = dy / d; + int len = (p->kind == 2) ? 32 : 14; + for (int t = 1; t <= len; t++) { + int tx = sx - (int)(ux * (float)t * 0.75f); + int ty = sy - (int)(uy * (float)t * 0.75f); + uint8_t fade = (uint8_t)(255 - t * 9); + if (p->kind == 2) { + disc(tx, ty, (t < 3) ? 2 : 1, fade, 80, 255); + px(tx, ty, 220, 180, 255); + } else { + disc(tx, ty, 1, 255, (uint8_t)(200 - t * 8), 50); + } } } } diff --git a/engine-c/src/units.c b/engine-c/src/units.c index 2cd02dd..cd88516 100644 --- a/engine-c/src/units.c +++ b/engine-c/src/units.c @@ -351,8 +351,8 @@ static void unit_combat_step(Game *g, Unit *u, float dt) { if ((tu || tb) && d <= rng) { u->heading = (float)atan2(ty - u->y, tx - u->x); if (u->atk_cd <= 0) { - int kind = (u->type == UT_ARTILLERY || u->type == UT_MISSILE || u->type == UT_TANK) - ? 1 : 0; + int kind = (u->type == UT_MISSILE) ? 3 + : (u->type == UT_ARTILLERY || u->type == UT_TANK) ? 1 : 0; /* muzzle offset along heading so shot/live FX sit at barrel tip */ float mx = u->x + cosf(u->heading) * 14.0f; float my = u->y + sinf(u->heading) * 14.0f;