diff --git a/engine-c/src/engine.c b/engine-c/src/engine.c index 5d78560..601f877 100644 --- a/engine-c/src/engine.c +++ b/engine-c/src/engine.c @@ -357,8 +357,82 @@ static void place_showcase(Game *g, Faction human, Faction enemy) { } } } + + /* scrap mound fields: SE of HQ + corridor toward primary clash */ + { + int hx_t = sx, hy_t = sy; + for (int dy = -2; dy <= 8; dy++) { + for (int dx = -2; dx <= 10; dx++) { + int tx = hx_t + dx, ty = hy_t + dy; + if (!map_in_bounds(&g->map, tx, ty)) continue; + Tile *t = map_tile(&g->map, tx, ty); + if (!t || t->occupied || !t->walkable) continue; + if (t->terrain == T_ROAD) continue; + int h = (dx * 11 + dy * 9) & 7; + if (h == 0 || h == 3) { + t->terrain = T_SCRAP; + t->scrap = (uint8_t)(110 + ((dx * 17 + dy * 13) & 127)); + } + } + } + } } + /* secondary clash front SE of HQ (second fight plate in crop) */ + float rx2 = wx + 40.0f, ry2 = wy + 70.0f; + { + int ctx2, cty2; + map_world_to_tile(rx2, ry2, &ctx2, &cty2); + for (int dy = -5; dy <= 5; dy++) { + for (int dx = -6; dx <= 6; dx++) { + int tx = ctx2 + dx, ty = cty2 + dy; + if (!map_in_bounds(&g->map, tx, ty)) continue; + Tile *t = map_tile(&g->map, tx, ty); + if (!t || !t->walkable) continue; + int d2 = dx * dx + dy * dy; + if (d2 <= 2) { + t->terrain = T_ROAD; + t->scrap = 0; + } else if (d2 <= 10) { + t->terrain = T_RUBBLE; + t->scrap = 0; + } else if (d2 <= 24 && ((dx + dy * 2) & 3) == 0) { + t->terrain = T_SCRAP; + t->scrap = (uint8_t)(100 + ((dx * 19 + dy * 11) & 127)); + } + } + } + /* short approach from HQ south gate toward secondary */ + { + int steps = abs(ctx2 - sx) + abs(cty2 - sy); + if (steps < 1) steps = 1; + for (int s = 0; s <= steps; s++) { + int tx = sx + (ctx2 - sx) * s / steps; + int ty = sy + (cty2 - sy) * s / steps; + if (!map_in_bounds(&g->map, tx, ty)) continue; + Tile *t = map_tile(&g->map, tx, ty); + if (!t || !t->walkable) continue; + if (t->occupied) continue; + t->terrain = T_ROAD; + t->scrap = 0; + } + } + bld_spawn(g, BT_TURRET, human, ctx2 - 2, cty2); + bld_spawn(g, BT_TURRET, human, ctx2 + 1, cty2 + 1); + bld_spawn(g, BT_WALL, human, ctx2 - 3, cty2); + bld_spawn(g, BT_WALL, human, ctx2 - 3, cty2 + 1); + bld_force_complete(g); + } + /* Survivor SE defenders */ + unit_spawn(g, UT_TANK, human, rx2 - 18, ry2 - 8); + unit_spawn(g, UT_TANK, human, rx2 - 6, ry2 + 10); + unit_spawn(g, UT_JEEP, human, rx2 - 28, ry2 + 4); + unit_spawn(g, UT_FLAME, human, rx2 - 22, ry2 + 18); + unit_spawn(g, UT_BUGGY, human, rx2 - 34, ry2 - 12); + for (int i = 0; i < 5; i++) + unit_spawn(g, UT_INFANTRY, human, + rx2 - 12 + (i % 3) * 12, ry2 - 16 + (i / 3) * 14); + int raid[56]; int rn = 0; for (int i = 0; i < 8; i++) @@ -389,6 +463,24 @@ static void place_showcase(Game *g, Faction human, Faction enemy) { raid[rn++] = unit_spawn(g, UT_INFANTRY, enemy, rx - 28 + (i % 4) * 14, ry - 8 + (i / 4) * 14); + /* secondary Evolved pack at SE clash (room left in raid[56]) */ + int rn_pri = rn; + for (int i = 0; i < 4; i++) + raid[rn++] = unit_spawn(g, UT_MUTANT, enemy, + rx2 + 10 + (i % 2) * 16, ry2 + 8 + (i / 2) * 14); + for (int i = 0; i < 2; i++) + raid[rn++] = unit_spawn(g, UT_BEAST, enemy, rx2 + 28 + i * 14, ry2 + 4 + i * 10); + raid[rn++] = unit_spawn(g, UT_BUGGY, enemy, rx2 + 18, ry2 - 10); + raid[rn++] = unit_spawn(g, UT_BUGGY, enemy, rx2 + 34, ry2 + 16); + raid[rn++] = unit_spawn(g, UT_JEEP, enemy, rx2 + 42, ry2 + 2); + raid[rn++] = unit_spawn(g, UT_TANK, enemy, rx2 + 50, ry2 + 12); + raid[rn++] = unit_spawn(g, UT_TANK, enemy, rx2 + 58, ry2 - 6); + raid[rn++] = unit_spawn(g, UT_RAVAGER, enemy, rx2 + 46, ry2 + 22); + raid[rn++] = unit_spawn(g, UT_FLAME, enemy, rx2 + 22, ry2 + 26); + raid[rn++] = unit_spawn(g, UT_MISSILE, enemy, rx2 + 64, ry2 + 8); + for (int i = 0; i < 2; i++) + raid[rn++] = unit_spawn(g, UT_INFANTRY, enemy, + rx2 + 6 + i * 12, ry2 + 20 + i * 6); /* seed Survivor harvester beds so cargo silhouette is visible in shot */ { @@ -404,66 +496,104 @@ static void place_showcase(Game *g, Faction human, Faction enemy) { } } - /* pre-damage for HP-bar variety (sim may add more) */ + /* pre-damage for HP-bar variety at both clash loci */ for (int i = 0; i < g->unit_count; i++) { Unit *u = &g->units[i]; if (u->dead || u->max_hp <= 0) continue; float dx = u->x - rx, dy = u->y - ry; - if (dx * dx + dy * dy > 140.0f * 140.0f) continue; + float dx2 = u->x - rx2, dy2 = u->y - ry2; + int near = (dx * dx + dy * dy <= 140.0f * 140.0f) + || (dx2 * dx2 + dy2 * dy2 <= 110.0f * 110.0f); + if (!near) continue; float f = 0.35f + 0.45f * ((i * 17) % 100) / 100.0f; u->hp = u->max_hp * f; } - /* FX seeds at the clash locus */ + /* FX seeds at primary + secondary clash loci */ particle_burst(g, rx, ry, 28, 1, 255, 140, 40); particle_burst(g, rx + 20, ry - 10, 18, 0, 90, 90, 90); particle_burst(g, rx - 15, ry + 12, 22, 2, 255, 220, 80); particle_burst(g, rx + 40, ry + 5, 14, 1, 255, 100, 30); particle_burst(g, rx + 10, ry - 25, 16, 3, 80, 70, 60); + particle_burst(g, rx2, ry2, 24, 1, 255, 130, 35); + particle_burst(g, rx2 + 16, ry2 - 8, 16, 0, 100, 95, 85); + particle_burst(g, rx2 - 12, ry2 + 10, 18, 2, 255, 210, 70); + particle_burst(g, rx2 + 28, ry2 + 6, 12, 1, 255, 90, 25); + particle_burst(g, rx2 + 8, ry2 + 20, 14, 3, 85, 75, 55); - /* point raid at nearest human combat units / HQ */ + /* point primary raid at nearest human combat / HQ; secondary at SE prey */ int hq = -1; for (int i = 0; i < g->bld_count; i++) if (!g->buildings[i].dead && g->buildings[i].faction == human && g->buildings[i].type == BT_BASE) { hq = i; break; } - int prey = -1; + int prey = -1, prey2 = -1; + float prey_d2 = 1e12f, prey2_d2 = 1e12f; for (int i = 0; i < g->unit_count; i++) { Unit *u = &g->units[i]; if (u->dead || u->faction != human) continue; if (u->type == UT_HARVESTER || u->type == UT_MEDIC) continue; - prey = i; break; + float d1 = (u->x - rx) * (u->x - rx) + (u->y - ry) * (u->y - ry); + float d2 = (u->x - rx2) * (u->x - rx2) + (u->y - ry2) * (u->y - ry2); + if (d1 < prey_d2) { prey_d2 = d1; prey = i; } + if (d2 < prey2_d2) { prey2_d2 = d2; prey2 = i; } + } + int tur2 = -1; + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || b->faction != human || b->type != BT_TURRET) continue; + float bx = (float)(b->tx * TILE + b->w * TILE / 2); + float by = (float)(b->ty * TILE + b->h * TILE / 2); + float d2 = (bx - rx2) * (bx - rx2) + (by - ry2) * (by - ry2); + if (d2 < 90.0f * 90.0f) { tur2 = i; break; } } for (int i = 0; i < rn; i++) { if (raid[i] < 0) continue; - if (prey >= 0) unit_issue_attack_unit(g, raid[i], prey); - else if (hq >= 0) unit_issue_attack_bld(g, raid[i], hq); + if (i < rn_pri) { + if (prey >= 0) unit_issue_attack_unit(g, raid[i], prey); + else if (hq >= 0) unit_issue_attack_bld(g, raid[i], hq); + } else { + if (prey2 >= 0) unit_issue_attack_unit(g, raid[i], prey2); + else if (tur2 >= 0) unit_issue_attack_bld(g, raid[i], tur2); + else if (hq >= 0) unit_issue_attack_bld(g, raid[i], hq); + } } - /* human combat units engage nearest raiders (spread targets) */ + /* human combat units engage nearest raiders (both fronts) */ int ridx = 0; for (int i = 0; i < g->unit_count; i++) { Unit *u = &g->units[i]; if (u->dead || u->faction != human) continue; if (u->type == UT_HARVESTER || u->type == UT_MEDIC) continue; float dx = u->x - rx, dy = u->y - ry; - if (dx * dx + dy * dy > 220.0f * 220.0f) continue; + float dx2 = u->x - rx2, dy2 = u->y - ry2; + int near = (dx * dx + dy * dy <= 220.0f * 220.0f) + || (dx2 * dx2 + dy2 * dy2 <= 160.0f * 160.0f); + if (!near) continue; + int prefer_sec = (dx2 * dx2 + dy2 * dy2) < (dx * dx + dy * dy); + int lo = prefer_sec ? rn_pri : 0; + int hi = prefer_sec ? rn : rn_pri; + if (hi <= lo) { lo = 0; hi = rn; } int tgt = -1; - for (int k = 0; k < rn; k++) { - int ri = raid[(ridx + k) % rn]; + int span = hi - lo; + for (int k = 0; k < span; k++) { + int ri = raid[lo + (ridx + k) % span]; if (ri >= 0 && !g->units[ri].dead) { tgt = ri; ridx += k + 1; break; } } if (tgt >= 0) unit_issue_attack_unit(g, i, tgt); } - /* select a front-line squad for yellow selection boxes in the shot */ + /* select front-line squads on both clashes for yellow selection boxes */ unit_clear_selection(g); int nsel = 0; - for (int i = 0; i < g->unit_count && nsel < 8; i++) { + for (int i = 0; i < g->unit_count && nsel < 10; i++) { Unit *u = &g->units[i]; if (u->dead || u->faction != human) continue; if (u->type == UT_HARVESTER || u->type == UT_MEDIC) continue; float dx = u->x - rx, dy = u->y - ry; - if (dx * dx + dy * dy > 120.0f * 120.0f) continue; + float dx2 = u->x - rx2, dy2 = u->y - ry2; + int near = (dx * dx + dy * dy <= 120.0f * 120.0f) + || (dx2 * dx2 + dy2 * dy2 <= 90.0f * 90.0f); + if (!near) continue; unit_select_single(g, i, 1); nsel++; } diff --git a/engine-c/src/main.c b/engine-c/src/main.c index 5eb7f5b..d351989 100644 --- a/engine-c/src/main.c +++ b/engine-c/src/main.c @@ -214,6 +214,76 @@ int main(int argc, char **argv) { spawned++; } } + /* secondary SE clash: lingering columns + tracers */ + float clash2_x = hx + 40.0f, clash2_y = hy + 70.0f; + float c2x = clash2_x, c2y = clash2_y; + int n2 = 0; + float sx2 = 0, sy2 = 0; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction == g->human) continue; + float dx = u->x - clash2_x, dy = u->y - clash2_y; + if (dx * dx + dy * dy > 140.0f * 140.0f) continue; + sx2 += u->x; sy2 += u->y; n2++; + } + if (n2 > 0) { c2x = sx2 / n2; c2y = sy2 / n2; } + particle_burst(g, c2x, c2y, 40, 1, 255, 135, 35); + particle_burst(g, c2x + 18, c2y - 10, 28, 0, 115, 105, 90); + particle_burst(g, c2x - 14, c2y + 12, 30, 2, 255, 220, 85); + particle_burst(g, c2x + 26, c2y + 8, 22, 1, 255, 95, 28); + particle_burst(g, c2x - 6, c2y - 20, 24, 3, 90, 78, 62); + particle_burst(g, c2x + 10, c2y + 24, 20, 1, 255, 160, 50); + { + int spawned2 = 0; + const float pair_r2 = 150.0f * 150.0f; + const float r2b = 140.0f * 140.0f; + for (int i = 0; i < g->unit_count && spawned2 < 36; 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 < 36 && shots < 2; 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 = (spawned2 % 4 == 0) ? 3 + : (spawned2 % 4 == 1) ? 1 + : (spawned2 % 4 == 2) ? 2 : 0; + float t = 0.20f + 0.55f * ((spawned2 * 41) % 100) / 100.0f; + bullet_spawn(g, a->x + dx * t, a->y + dy * t, + b->id, -1, 1.0f, a->faction, kind); + spawned2++; + shots++; + } + } + for (int i = 0; i < g->bld_count && spawned2 < 44; i++) { + Building *bl = &g->buildings[i]; + if (bl->dead || (bl->type != BT_TURRET && bl->type != BT_BASE)) + continue; + float bx = (float)(bl->tx * TILE + bl->w * TILE / 2); + float by = (float)(bl->ty * TILE + bl->h * TILE / 2); + float bdx = bx - clash2_x, bdy = by - clash2_y; + if (bdx * bdx + bdy * bdy > r2b) continue; + int best = -1; float bestd = 1e12f; + for (int j = 0; j < g->unit_count; j++) { + Unit *u = &g->units[j]; + if (u->dead || u->faction == bl->faction) continue; + float dx = u->x - bx, dy = u->y - by; + float d2 = dx * dx + dy * dy; + if (d2 < bestd && d2 < pair_r2) { bestd = d2; best = j; } + } + if (best < 0) continue; + Unit *tgt = &g->units[best]; + float dx = tgt->x - bx, dy = tgt->y - by; + float t = 0.28f + 0.42f * ((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); + spawned2++; + } + } } char path[64]; snprintf(path, sizeof path, "shot_%04d.png", 1); snapshot_capture(g, path); diff --git a/engine-c/src/projectiles.c b/engine-c/src/projectiles.c index fc19388..4d688f2 100644 --- a/engine-c/src/projectiles.c +++ b/engine-c/src/projectiles.c @@ -88,7 +88,10 @@ void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, float sp = rng_frange(&g_prng, 10, 60); p->vx = (float)cosf(ang) * sp; p->vy = (float)sinf(ang) * sp; - p->life = p->max_life = (kind == 0) ? 1.2f : 0.5f; + /* lingering columns: smoke / fire / spark|debris (KKND2 clash plate) */ + if (kind == 0) p->life = p->max_life = 2.8f; + else if (kind == 1) p->life = p->max_life = 1.8f; + else p->life = p->max_life = 1.0f; p->r = cr; p->g = cg; p->b = cb; p->kind = kind; } @@ -105,6 +108,7 @@ void particle_update(Game *g, float dt) { p->y += p->vy * dt; p->vx *= 0.92f; p->vy *= 0.92f; if (p->kind == 1) p->vy += 40 * dt; /* fire rises/slows */ + if (p->kind == 0) p->vy -= 18 * dt; /* smoke drifts up */ if (p->life <= 0) { g->particles[i] = g->particles[g->particle_count - 1]; g->particle_count--; diff --git a/engine-c/src/snapshot.c b/engine-c/src/snapshot.c index dbd46dc..ed3ecdd 100644 --- a/engine-c/src/snapshot.c +++ b/engine-c/src/snapshot.c @@ -638,9 +638,19 @@ void snapshot_capture(Game *g, const char *path) { line(sx + TILE/2, sy + 2, sx + TILE/2, sy + TILE - 3, 165, 140, 95); } if (t->terrain == T_SCRAP && t->scrap > 0) { - int cc = (sx+TILE/2), cr = (sy+TILE/2); - for (int k=0;kscrap/16;k++) - disc(cc + ((k*7)%12)-6, cr + ((k*5)%12)-6, 3, 235,190,85); + /* stacked scrap mounds (KKND2 resource piles) */ + int cc = sx + TILE / 2, cr = sy + TILE / 2 + 2; + int n = 3 + t->scrap / 28; + if (n > 9) n = 9; + for (int k = 0; k < n; k++) { + int ox = ((k * 7) % 14) - 7; + int oy = ((k * 5) % 10) - 5 - (k / 3) * 2; + int rad = 4 + (k & 1) + (t->scrap > 120 ? 1 : 0); + disc(cc + ox, cr + oy, rad, 235, 190, 85); + disc(cc + ox - 1, cr + oy - rad / 2, rad - 1, 210, 165, 70); + disc(cc + ox + 1, cr + oy - rad + 1, (rad > 3) ? rad - 2 : 2, + 255, 215, 120); + } } } } diff --git a/engine-c/src/sprites.c b/engine-c/src/sprites.c index 6f81916..0dae594 100644 --- a/engine-c/src/sprites.c +++ b/engine-c/src/sprites.c @@ -415,9 +415,16 @@ SDL_Texture *sprites_make_terrain(SDL_Renderer *r, TerrainType t) { (Uint8)kknd_clampi(tb+v,0,255), 255); } if (t == T_SCRAP) { - for (int i = 0; i < 10; i++) { - int x = (i*97) % TILE, y = (i*61) % TILE; - disc(s, x, y, 2, 180, 150, 60, 255); + /* stacked scrap mounds (live parity with snapshot) */ + int cc = TILE / 2, cr = TILE / 2 + 2; + for (int k = 0; k < 7; k++) { + int ox = ((k * 7) % 14) - 7; + int oy = ((k * 5) % 10) - 5 - (k / 3) * 2; + int rad = 3 + (k & 1); + disc(s, cc + ox, cr + oy, rad, 235, 190, 85, 255); + disc(s, cc + ox - 1, cr + oy - rad / 2, rad - 1, 210, 165, 70, 255); + disc(s, cc + ox + 1, cr + oy - rad + 1, + (rad > 2) ? rad - 2 : 1, 255, 215, 120, 255); } } if (t == T_WATER) {