Snapshot/live wreck silhouettes (type-aware hulls, scorch/smoke), seed ~22 clash wrecks in place_showcase, reset wreck ring on match setup.
833 lines
36 KiB
C
833 lines
36 KiB
C
#include "kknd.h"
|
|
|
|
/* =========================================================================
|
|
* engine.c - Match setup, main simulation step and win/lose logic.
|
|
* ========================================================================= */
|
|
|
|
int g_headless = 0;
|
|
int g_auto = 0;
|
|
|
|
void engine_log(const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
char buf[512];
|
|
vsnprintf(buf, sizeof buf, fmt, ap);
|
|
va_end(ap);
|
|
printf("[kknd] %s\n", buf);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void game_log(Game *g, const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(g->log_msg[g->log_next], 96, fmt, ap);
|
|
va_end(ap);
|
|
g->log_next = (g->log_next + 1) % 32;
|
|
if (g->log_count < 32) g->log_count++;
|
|
}
|
|
|
|
void engine_init(Game *g, const char *title) {
|
|
memset(g, 0, sizeof(Game));
|
|
g->wrecks = (Wreck *)calloc(MAX_WRECKS, sizeof(Wreck));
|
|
config_init();
|
|
if (!g_headless) {
|
|
audio_init();
|
|
if (!render_init(g)) {
|
|
fprintf(stderr, "Failed to initialise renderer\n");
|
|
exit(1);
|
|
}
|
|
} else {
|
|
(void)title;
|
|
}
|
|
g->state = GAME_MENU;
|
|
g->human = FACTION_SURVIVOR;
|
|
g->diff = DIFF_NORMAL;
|
|
g->speed_mul = 1.0f;
|
|
g->unlocked = 1;
|
|
g->menu_sel = 0;
|
|
}
|
|
|
|
void engine_shutdown(Game *g) {
|
|
free(g->wrecks);
|
|
sprites_free(&g->spr);
|
|
if (g->ren) SDL_DestroyRenderer(g->ren);
|
|
if (g->win) SDL_DestroyWindow(g->win);
|
|
audio_shutdown();
|
|
SDL_Quit();
|
|
map_free(&g->map);
|
|
}
|
|
|
|
static void bld_force_complete(Game *g) {
|
|
for (int i = 0; i < g->bld_count; i++) {
|
|
Building *b = &g->buildings[i];
|
|
if (b->dead) continue;
|
|
b->build = 1.0f;
|
|
b->hp = b->max_hp;
|
|
}
|
|
}
|
|
|
|
/* After force-complete: leave a few incomplete yards + battle-worn
|
|
* fortifications near the raid locus so scaffold/damage FX show in KKND_SHOT. */
|
|
static void showcase_bld_battle_wear(Game *g) {
|
|
int incomplete_n = 0, damaged_n = 0;
|
|
float wx, wy;
|
|
map_tile_to_world(g->map.spawn_x[g->human], g->map.spawn_y[g->human], &wx, &wy);
|
|
float rx = wx + 88.0f, ry = wy - 48.0f;
|
|
for (int i = 0; i < g->bld_count; i++) {
|
|
Building *b = &g->buildings[i];
|
|
if (b->dead) continue;
|
|
float cx = (b->tx + b->w * 0.5f) * (float)TILE;
|
|
float cy = (b->ty + b->h * 0.5f) * (float)TILE;
|
|
float dx = cx - rx, dy = cy - ry;
|
|
float d2 = dx * dx + dy * dy;
|
|
/* mid-base construction yards (away from clash) */
|
|
if (incomplete_n < 2 && b->faction == g->human && d2 > 180.0f * 180.0f &&
|
|
(b->type == BT_REPAIR || b->type == BT_RESEARCH || b->type == BT_WARFACTORY)) {
|
|
b->build = (incomplete_n == 0) ? 0.55f : 0.72f;
|
|
b->hp = b->max_hp * b->build * 0.9f;
|
|
incomplete_n++;
|
|
continue;
|
|
}
|
|
/* clash fortifications: turrets / walls / power take hits */
|
|
if (damaged_n < 6 && d2 < 160.0f * 160.0f &&
|
|
(b->type == BT_TURRET || b->type == BT_WALL || b->type == BT_POWER)) {
|
|
float frac = (damaged_n % 3 == 0) ? 0.35f
|
|
: (damaged_n % 3 == 1) ? 0.55f : 0.72f;
|
|
b->hp = b->max_hp * frac;
|
|
damaged_n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void place_start(Game *g, Faction f) {
|
|
int sx = g->map.spawn_x[f], sy = g->map.spawn_y[f];
|
|
int bx = sx - 1, by = sy - 1;
|
|
bld_spawn(g, BT_BASE, f, bx, by);
|
|
bld_spawn(g, BT_REFINERY, f, bx, sy + 2);
|
|
bld_spawn(g, BT_WARFACTORY, f, sx + 2, by);
|
|
bld_spawn(g, BT_POWER, f, sx + 2, sy + 2);
|
|
|
|
float wx, wy; map_tile_to_world(sx, sy, &wx, &wy);
|
|
/* harvesters */
|
|
for (int i = 0; i < 2; i++)
|
|
unit_spawn(g, UT_HARVESTER, f, wx + (i*24-12), wy + 30);
|
|
/* starting infantry */
|
|
for (int i = 0; i < 4; i++)
|
|
unit_spawn(g, UT_INFANTRY, f, wx - 40 + i*20, wy - 20);
|
|
/* a couple of buggies */
|
|
for (int i = 0; i < 2; i++)
|
|
unit_spawn(g, UT_BUGGY, f, wx - 30 + i*30, wy - 40);
|
|
}
|
|
|
|
/* KKND2-like showcase: extra human base structures + diverse roster +
|
|
* enemy raid pack near the camera so fog/snapshot show combat. */
|
|
/* Seed a battlefield wreck (type-aware hulls drawn in snapshot/live render). */
|
|
static void spawn_showcase_wreck(Game *g, UnitType type, Faction faction,
|
|
float x, float y, float rot) {
|
|
if (g->wreck_count >= MAX_WRECKS) return;
|
|
Wreck *w = &g->wrecks[g->wreck_next];
|
|
w->x = x; w->y = y; w->type = type;
|
|
w->faction = faction; w->rot = rot;
|
|
g->wreck_next = (g->wreck_next + 1) % MAX_WRECKS;
|
|
if (g->wreck_count < MAX_WRECKS) g->wreck_count++;
|
|
}
|
|
|
|
/* ~22 charred hulls around primary + secondary clash for KKND2 plate shots. */
|
|
static void seed_clash_wrecks(Game *g, Faction human, Faction enemy,
|
|
float rx, float ry, float rx2, float ry2) {
|
|
/* primary NE clash — mixed sides, varied chassis */
|
|
spawn_showcase_wreck(g, UT_TANK, human, rx - 22, ry + 8, 0.6f);
|
|
spawn_showcase_wreck(g, UT_TANK, enemy, rx + 14, ry - 12, 2.4f);
|
|
spawn_showcase_wreck(g, UT_RAVAGER, enemy, rx + 28, ry + 6, 1.1f);
|
|
spawn_showcase_wreck(g, UT_BUGGY, human, rx - 8, ry - 18, 3.5f);
|
|
spawn_showcase_wreck(g, UT_BUGGY, enemy, rx + 6, ry + 20, 0.2f);
|
|
spawn_showcase_wreck(g, UT_JEEP, human, rx - 30, ry - 4, 4.8f);
|
|
spawn_showcase_wreck(g, UT_ARTILLERY, human, rx - 40, ry + 16, 5.2f);
|
|
spawn_showcase_wreck(g, UT_FLAME, enemy, rx + 36, ry - 8, 2.0f);
|
|
spawn_showcase_wreck(g, UT_MISSILE, enemy, rx + 18, ry + 28, 1.7f);
|
|
spawn_showcase_wreck(g, UT_INFANTRY, human, rx - 14, ry + 24, 0.9f);
|
|
spawn_showcase_wreck(g, UT_INFANTRY, enemy, rx + 4, ry - 26, 3.1f);
|
|
spawn_showcase_wreck(g, UT_MUTANT, enemy, rx + 22, ry + 14, 4.2f);
|
|
spawn_showcase_wreck(g, UT_BEAST, enemy, rx + 42, ry + 10, 0.4f);
|
|
spawn_showcase_wreck(g, UT_HARVESTER, human, rx - 48, ry - 10, 5.9f);
|
|
/* secondary SE clash */
|
|
spawn_showcase_wreck(g, UT_TANK, human, rx2 - 16, ry2 + 6, 1.3f);
|
|
spawn_showcase_wreck(g, UT_TANK, enemy, rx2 + 12, ry2 - 8, 3.8f);
|
|
spawn_showcase_wreck(g, UT_BUGGY, enemy, rx2 + 22, ry2 + 14, 0.7f);
|
|
spawn_showcase_wreck(g, UT_JEEP, human, rx2 - 24, ry2 - 4, 2.6f);
|
|
spawn_showcase_wreck(g, UT_FLAME, human, rx2 - 8, ry2 + 18, 4.5f);
|
|
spawn_showcase_wreck(g, UT_RAVAGER, enemy, rx2 + 30, ry2 + 4, 1.9f);
|
|
spawn_showcase_wreck(g, UT_INFANTRY, human, rx2 + 4, ry2 - 16, 5.5f);
|
|
spawn_showcase_wreck(g, UT_MUTANT, enemy, rx2 + 16, ry2 + 22, 0.1f);
|
|
}
|
|
|
|
static void place_showcase(Game *g, Faction human, Faction enemy) {
|
|
int sx = g->map.spawn_x[human], sy = g->map.spawn_y[human];
|
|
float wx, wy; map_tile_to_world(sx, sy, &wx, &wy);
|
|
|
|
/* extra structures in the cleared spawn pad (±8) */
|
|
bld_spawn(g, BT_REPAIR, human, sx - 4, sy - 1);
|
|
bld_spawn(g, BT_RESEARCH, human, sx - 4, sy + 2);
|
|
bld_spawn(g, BT_POWER, human, sx + 3, sy + 2);
|
|
bld_spawn(g, BT_POWER, human, sx + 5, sy + 2);
|
|
bld_spawn(g, BT_WARFACTORY, human, sx + 3, sy - 4);
|
|
|
|
/* turrets covering approaches */
|
|
bld_spawn(g, BT_TURRET, human, sx - 2, sy - 3);
|
|
bld_spawn(g, BT_TURRET, human, sx + 1, sy - 3);
|
|
bld_spawn(g, BT_TURRET, human, sx + 4, sy);
|
|
bld_spawn(g, BT_TURRET, human, sx + 4, sy + 2);
|
|
bld_spawn(g, BT_TURRET, human, sx - 2, sy + 4);
|
|
bld_spawn(g, BT_TURRET, human, sx + 2, sy + 4);
|
|
bld_spawn(g, BT_TURRET, human, sx - 4, sy);
|
|
|
|
/* blast-wall spur (north + west + east stub) */
|
|
for (int dx = -3; dx <= 3; dx++) {
|
|
if (dx >= -1 && dx <= 1) continue; /* gate */
|
|
bld_spawn(g, BT_WALL, human, sx + dx, sy - 4);
|
|
}
|
|
for (int dy = -3; dy <= 3; dy++) {
|
|
if (dy >= -1 && dy <= 1) continue;
|
|
bld_spawn(g, BT_WALL, human, sx - 5, sy + dy);
|
|
}
|
|
for (int dy = -2; dy <= 2; dy++) {
|
|
if (dy == 0) continue;
|
|
bld_spawn(g, BT_WALL, human, sx + 6, sy + dy);
|
|
}
|
|
|
|
/* dense Survivor battle line NE of HQ (tight combat camera) */
|
|
unit_spawn(g, UT_TANK, human, wx + 48, wy - 8);
|
|
unit_spawn(g, UT_TANK, human, wx + 68, wy + 8);
|
|
unit_spawn(g, UT_TANK, human, wx + 82, wy - 38);
|
|
unit_spawn(g, UT_TANK, human, wx + 58, wy - 48);
|
|
unit_spawn(g, UT_RAVAGER, human, wx + 92, wy - 22);
|
|
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_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_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);
|
|
unit_spawn(g, UT_BUGGY, human, wx + 72, wy - 72);
|
|
unit_spawn(g, UT_MEDIC, human, wx - 12, wy + 40);
|
|
unit_spawn(g, UT_MEDIC, human, wx + 20, wy + 28);
|
|
for (int i = 0; i < 10; i++)
|
|
unit_spawn(g, UT_INFANTRY, human, wx + 88 + (i % 5) * 14, wy + 8 + (i / 5) * 16);
|
|
|
|
/* Evolved raid pack — close NE, overlapping Survivor line */
|
|
float rx = wx + 88.0f, ry = wy - 48.0f;
|
|
|
|
/* forward fortifications at the clash so a tight combat crop still shows buildings */
|
|
{
|
|
int ftx, fty;
|
|
map_world_to_tile(rx, ry, &ftx, &fty);
|
|
bld_spawn(g, BT_TURRET, human, ftx - 2, fty + 1);
|
|
bld_spawn(g, BT_TURRET, human, ftx + 1, fty + 2);
|
|
bld_spawn(g, BT_TURRET, human, ftx - 1, fty - 2);
|
|
bld_spawn(g, BT_TURRET, human, ftx + 2, fty - 1);
|
|
bld_spawn(g, BT_WALL, human, ftx - 3, fty);
|
|
bld_spawn(g, BT_WALL, human, ftx - 3, fty + 1);
|
|
bld_spawn(g, BT_WALL, human, ftx - 3, fty - 1);
|
|
bld_spawn(g, BT_WALL, human, ftx + 2, fty + 1);
|
|
bld_spawn(g, BT_WALL, human, ftx + 2, fty);
|
|
bld_spawn(g, BT_WALL, human, ftx + 3, fty - 1);
|
|
bld_spawn(g, BT_POWER, human, ftx - 4, fty + 2);
|
|
bld_force_complete(g); /* complete any partial builds from spawn */
|
|
}
|
|
|
|
/* denser clash scars: road core + rubble ring + scrap flecks (arid fight plate) */
|
|
{
|
|
int ctx, cty;
|
|
map_world_to_tile(rx, ry, &ctx, &cty);
|
|
for (int dy = -6; dy <= 6; dy++) {
|
|
for (int dx = -7; dx <= 7; dx++) {
|
|
int tx = ctx + dx, ty = cty + 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 <= 3) {
|
|
t->terrain = T_ROAD;
|
|
t->scrap = 0;
|
|
} else if (d2 <= 12) {
|
|
t->terrain = T_RUBBLE;
|
|
t->scrap = 0;
|
|
} else if (d2 <= 28 && ((dx + dy * 3) & 3) == 0) {
|
|
t->terrain = T_SCRAP;
|
|
t->scrap = (uint8_t)(90 + ((dx * 13 + dy * 7) & 127));
|
|
} else if (d2 <= 32 && ((dx * 5 + dy) & 2) == 0) {
|
|
t->terrain = T_RUBBLE;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
}
|
|
/* approach road from HQ toward clash */
|
|
int hx_t = sx, hy_t = sy;
|
|
int steps = abs(ctx - hx_t) + abs(cty - hy_t);
|
|
if (steps < 1) steps = 1;
|
|
for (int s = 0; s <= steps; s++) {
|
|
int tx = hx_t + (ctx - hx_t) * s / steps;
|
|
int ty = hy_t + (cty - hy_t) * s / steps;
|
|
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
|
Tile *t = map_tile(&g->map, tx, ty);
|
|
if (!t || !t->walkable) continue;
|
|
t->terrain = T_ROAD;
|
|
t->scrap = 0;
|
|
}
|
|
|
|
/* Evolved forward outpost NW of clash (visible in combat crop).
|
|
* Clear pad to wasteland BEFORE buildings; ROCK scars only after
|
|
* (T_ROCK is non-walkable and bld_can_place rejects it). */
|
|
{
|
|
int erx = ctx - 10, ery = cty - 9;
|
|
for (int dy = -3; dy <= 5; dy++) {
|
|
for (int dx = -3; dx <= 6; dx++) {
|
|
int tx = erx + dx, ty = ery + dy;
|
|
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
|
Tile *t = map_tile(&g->map, tx, ty);
|
|
if (!t) continue;
|
|
if (t->occupied) continue;
|
|
t->terrain = T_WASTELAND;
|
|
t->walkable = 1;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
bld_spawn(g, BT_BASE, enemy, erx, ery);
|
|
bld_spawn(g, BT_BASE, enemy, erx + 3, ery);
|
|
bld_spawn(g, BT_POWER, enemy, erx - 1, ery + 2);
|
|
bld_spawn(g, BT_POWER, enemy, erx + 5, ery + 2);
|
|
bld_spawn(g, BT_WARFACTORY, enemy, erx, ery + 3);
|
|
bld_spawn(g, BT_WARFACTORY, enemy, erx + 3, ery + 3);
|
|
bld_spawn(g, BT_RESEARCH, enemy, erx - 2, ery);
|
|
bld_spawn(g, BT_REFINERY, enemy, erx + 5, ery + 3);
|
|
bld_spawn(g, BT_TURRET, enemy, erx + 2, ery - 2);
|
|
bld_spawn(g, BT_TURRET, enemy, erx - 2, ery + 3);
|
|
bld_spawn(g, BT_TURRET, enemy, erx + 6, ery + 1);
|
|
/* denser Evolved wall perimeter (N/W/E/S stubs + SE gate) */
|
|
bld_spawn(g, BT_WALL, enemy, erx - 2, ery - 1);
|
|
bld_spawn(g, BT_WALL, enemy, erx - 1, ery - 1);
|
|
bld_spawn(g, BT_WALL, enemy, erx, ery - 2);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 1, ery - 2);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 4, ery - 2);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 5, ery - 1);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 6, ery - 1);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 7, ery);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 7, ery + 1);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 7, ery + 2);
|
|
bld_spawn(g, BT_WALL, enemy, erx - 3, ery);
|
|
bld_spawn(g, BT_WALL, enemy, erx - 3, ery + 1);
|
|
bld_spawn(g, BT_WALL, enemy, erx - 3, ery + 2);
|
|
bld_spawn(g, BT_WALL, enemy, erx - 1, ery + 5);
|
|
bld_spawn(g, BT_WALL, enemy, erx, ery + 5);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 2, ery + 5);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 4, ery + 5);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 6, ery + 4);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 5, ery + 5);
|
|
/* SE approach gate toward clash */
|
|
bld_spawn(g, BT_WALL, enemy, erx + 8, ery + 3);
|
|
bld_spawn(g, BT_WALL, enemy, erx + 8, ery + 5);
|
|
bld_spawn(g, BT_TURRET, enemy, erx + 8, ery + 4);
|
|
bld_force_complete(g);
|
|
|
|
/* pad roads: internal lanes + corridor toward clash core */
|
|
for (int dy = 0; dy <= 4; dy++) {
|
|
for (int dx = -1; dx <= 5; dx++) {
|
|
int tx = erx + dx, ty = ery + 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 (dy == 1 || dy == 3 || dx == 1 || dx == 4) {
|
|
t->terrain = T_ROAD;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
int steps = abs(ctx - (erx + 6)) + abs(cty - (ery + 4));
|
|
if (steps < 1) steps = 1;
|
|
for (int s = 0; s <= steps; s++) {
|
|
int tx = (erx + 6) + (ctx - (erx + 6)) * s / steps;
|
|
int ty = (ery + 4) + (cty - (ery + 4)) * s / steps;
|
|
for (int o = -1; o <= 1; o++) {
|
|
int ox = tx + ((s & 1) ? 0 : o);
|
|
int oy = ty + ((s & 1) ? o : 0);
|
|
if (!map_in_bounds(&g->map, ox, oy)) continue;
|
|
Tile *t = map_tile(&g->map, ox, oy);
|
|
if (!t || t->occupied || !t->walkable) continue;
|
|
t->terrain = T_ROAD;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* outer ROCK / crater flecks — skip occupied tiles */
|
|
for (int dy = -5; dy <= 7; dy++) {
|
|
for (int dx = -5; dx <= 8; dx++) {
|
|
int tx = erx + dx, ty = ery + dy;
|
|
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
|
Tile *t = map_tile(&g->map, tx, ty);
|
|
if (!t || t->occupied) continue;
|
|
int d2 = dx * dx + dy * dy;
|
|
if (d2 < 16 || d2 > 55) continue;
|
|
if (((dx * 7 + dy * 11) & 5) != 0) continue;
|
|
t->terrain = T_ROCK;
|
|
t->walkable = 0;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
/* crater rubble + scrap flecks between Evolved pad and clash */
|
|
for (int dy = -3; dy <= 6; dy++) {
|
|
for (int dx = 3; dx <= 14; dx++) {
|
|
int tx = erx + dx, ty = ery + dy + 3;
|
|
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 * 13 + dy * 7) & 7;
|
|
if (h == 0) {
|
|
t->terrain = T_SCRAP;
|
|
t->scrap = (uint8_t)(70 + ((dx * 11 + dy * 5) & 127));
|
|
} else if (h <= 3) {
|
|
t->terrain = T_RUBBLE;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* denser corridor fill HQ → primary clash: scrap mounds, rubble, rock outcrops
|
|
* so arid yellow plate is broken up (KKND2 wasteland read). */
|
|
{
|
|
int hx_t = sx, hy_t = sy;
|
|
int minx = hx_t < ctx ? hx_t : ctx;
|
|
int maxx = hx_t > ctx ? hx_t : ctx;
|
|
int miny = hy_t < cty ? hy_t : cty;
|
|
int maxy = hy_t > cty ? hy_t : cty;
|
|
minx -= 4; maxx += 6; miny -= 5; maxy += 4;
|
|
for (int ty = miny; ty <= maxy; ty++) {
|
|
for (int tx = minx; tx <= maxx; tx++) {
|
|
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;
|
|
/* keep clash-core scars already set */
|
|
if (t->terrain == T_RUBBLE || t->terrain == T_SCRAP) continue;
|
|
int dx = tx - hx_t, dy = ty - hy_t;
|
|
int h = (tx * 17 + ty * 13) & 15;
|
|
int along = abs((ty - hy_t) * (ctx - hx_t) - (tx - hx_t) * (cty - hy_t));
|
|
int span = abs(ctx - hx_t) + abs(cty - hy_t);
|
|
if (span < 1) span = 1;
|
|
/* band around approach road */
|
|
if (along > span * 3) continue;
|
|
if (h == 0 || h == 7) {
|
|
t->terrain = T_SCRAP;
|
|
t->scrap = (uint8_t)(95 + ((dx * 19 + dy * 11) & 127));
|
|
} else if (h == 2 || h == 9) {
|
|
t->terrain = T_RUBBLE;
|
|
t->scrap = 0;
|
|
} else if (h == 4 && ((tx + ty) & 3) == 0) {
|
|
/* sparse rock outcrops — non-walkable */
|
|
t->terrain = T_ROCK;
|
|
t->walkable = 0;
|
|
t->scrap = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
/* denser corridor fill HQ → secondary SE clash */
|
|
{
|
|
int hx_t = sx, hy_t = sy;
|
|
int minx = hx_t < ctx2 ? hx_t : ctx2;
|
|
int maxx = hx_t > ctx2 ? hx_t : ctx2;
|
|
int miny = hy_t < cty2 ? hy_t : cty2;
|
|
int maxy = hy_t > cty2 ? hy_t : cty2;
|
|
minx -= 3; maxx += 5; miny -= 3; maxy += 5;
|
|
for (int ty = miny; ty <= maxy; ty++) {
|
|
for (int tx = minx; tx <= maxx; tx++) {
|
|
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 || t->terrain == T_RUBBLE || t->terrain == T_SCRAP)
|
|
continue;
|
|
int dx = tx - hx_t, dy = ty - hy_t;
|
|
int h = (tx * 19 + ty * 11) & 15;
|
|
int along = abs((ty - hy_t) * (ctx2 - hx_t) - (tx - hx_t) * (cty2 - hy_t));
|
|
int span = abs(ctx2 - hx_t) + abs(cty2 - hy_t);
|
|
if (span < 1) span = 1;
|
|
if (along > span * 3) continue;
|
|
if (h == 1 || h == 8) {
|
|
t->terrain = T_SCRAP;
|
|
t->scrap = (uint8_t)(90 + ((dx * 23 + dy * 7) & 127));
|
|
} else if (h == 3 || h == 10) {
|
|
t->terrain = T_RUBBLE;
|
|
t->scrap = 0;
|
|
} else if (h == 5 && ((tx + ty) & 3) == 1) {
|
|
t->terrain = T_ROCK;
|
|
t->walkable = 0;
|
|
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++)
|
|
raid[rn++] = unit_spawn(g, UT_MUTANT, enemy,
|
|
rx + (i % 4) * 18 - 10, ry + (i / 4) * 16 - 8);
|
|
for (int i = 0; i < 4; i++)
|
|
raid[rn++] = unit_spawn(g, UT_BEAST, enemy, rx + 30 + i * 16, ry - 14 + (i & 1) * 18);
|
|
raid[rn++] = unit_spawn(g, UT_BUGGY, enemy, rx + 8, ry - 28);
|
|
raid[rn++] = unit_spawn(g, UT_BUGGY, enemy, rx + 28, ry - 34);
|
|
raid[rn++] = unit_spawn(g, UT_BUGGY, enemy, rx + 48, ry - 22);
|
|
raid[rn++] = unit_spawn(g, UT_JEEP, enemy, rx + 16, ry + 18);
|
|
raid[rn++] = unit_spawn(g, UT_JEEP, enemy, rx + 36, ry + 8);
|
|
raid[rn++] = unit_spawn(g, UT_TANK, enemy, rx + 52, ry + 4);
|
|
raid[rn++] = unit_spawn(g, UT_TANK, enemy, rx + 68, ry - 12);
|
|
raid[rn++] = unit_spawn(g, UT_TANK, enemy, rx + 80, ry + 18);
|
|
raid[rn++] = unit_spawn(g, UT_RAVAGER, enemy, rx + 58, ry - 36);
|
|
raid[rn++] = unit_spawn(g, UT_RAVAGER, enemy, rx + 74, ry - 24);
|
|
raid[rn++] = unit_spawn(g, UT_RAVAGER, enemy, rx + 42, ry - 44);
|
|
raid[rn++] = unit_spawn(g, UT_FLAME, enemy, rx + 22, ry + 28);
|
|
raid[rn++] = unit_spawn(g, UT_FLAME, enemy, rx + 40, ry + 34);
|
|
raid[rn++] = unit_spawn(g, UT_FLAME, enemy, rx + 8, ry + 40);
|
|
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_ARTILLERY, enemy, rx + 85, ry + 10);
|
|
raid[rn++] = unit_spawn(g, UT_ARTILLERY, enemy, rx + 98, ry - 18);
|
|
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);
|
|
|
|
/* 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 */
|
|
{
|
|
int hi = 0;
|
|
for (int i = 0; i < g->unit_count; i++) {
|
|
Unit *u = &g->units[i];
|
|
if (u->dead || u->type != UT_HARVESTER || u->faction != human) continue;
|
|
float cap = (float)unit_def(u->type, u->faction)->cargo;
|
|
/* stagger: empty-ish / mid / full across harvesters */
|
|
float fracs[] = { 0.25f, 0.70f, 1.0f, 0.45f };
|
|
u->cargo = cap * fracs[hi % 4];
|
|
hi++;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
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 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 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, 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;
|
|
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 (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 (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;
|
|
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;
|
|
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 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 < 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;
|
|
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++;
|
|
}
|
|
|
|
/* lasting hulls already on the plate (SHOT_T too short for natural deaths) */
|
|
seed_clash_wrecks(g, human, enemy, rx, ry, rx2, ry2);
|
|
}
|
|
|
|
void engine_setup_match(Game *g, int mission_id) {
|
|
const Mission *m = mission_get(mission_id);
|
|
g->mission_id = m->id;
|
|
g->objective = m->objective;
|
|
g->diff = (Difficulty)m->diff;
|
|
g->obj_timer = 0;
|
|
|
|
/* resolve the enemy faction (never the player's own side) */
|
|
Faction enemy = m->enemy;
|
|
if (enemy == g->human)
|
|
enemy = (g->human == FACTION_SURVIVOR) ? FACTION_EVOLVED : FACTION_SURVIVOR;
|
|
g->enemy = enemy;
|
|
|
|
RNG rng;
|
|
rng_seed(&rng, (uint64_t)m->seed ^ 0x4B4B4E44ULL);
|
|
g->map.style = m->map_style;
|
|
map_init(&g->map, &rng);
|
|
|
|
g->unit_count = 0; g->unit_next_id = 1;
|
|
g->bld_count = 0; g->bld_next_id = 1;
|
|
g->bullet_count = 0; g->particle_count = 0;
|
|
g->wreck_count = 0; g->wreck_next = 0;
|
|
g->sel_count = 0; g->placing = 0;
|
|
g->time = 0; g->frames = 0; g->winner = 0;
|
|
g->ai_timer = 5.0f;
|
|
g->log_count = 0; g->log_next = 0;
|
|
|
|
for (int f = 1; f < MAX_FACTIONS; f++) {
|
|
g->scrap[f] = START_SCRAP;
|
|
g->power[f] = 0; g->pop[f] = 0; g->pop_cap[f] = 0;
|
|
for (int u = 0; u < UP_COUNT; u++) g->upg[f].lvl[u] = 0;
|
|
g->research_lab[f] = 0;
|
|
g->research_cur[f] = -1;
|
|
g->research_prog[f] = 0;
|
|
}
|
|
|
|
place_start(g, g->human);
|
|
place_start(g, enemy);
|
|
place_showcase(g, g->human, enemy);
|
|
bld_force_complete(g);
|
|
|
|
/* fortress theme: wall the enemy base (leave a gate on the far edge) */
|
|
if (m->map_style == 3) {
|
|
int sx = g->map.spawn_x[enemy], sy = g->map.spawn_y[enemy];
|
|
for (int dx = -2; dx <= 4; dx++)
|
|
for (int dy = -2; dy <= 4; dy++) {
|
|
int perimeter = (dx == -2 || dx == 4 || dy == -2 || dy == 4);
|
|
if (!perimeter) continue;
|
|
if (dy == 4) continue; /* gate on the far edge */
|
|
bld_spawn(g, BT_WALL, enemy, sx + dx, sy + dy);
|
|
}
|
|
bld_force_complete(g);
|
|
}
|
|
|
|
/* demo incomplete yards + damaged clash fortifications for scaffold/FX */
|
|
showcase_bld_battle_wear(g);
|
|
|
|
/* centre camera on player base */
|
|
int hx = g->map.spawn_x[g->human], hy = g->map.spawn_y[g->human];
|
|
g->cam.x = (float)(hx*TILE - VIEW_W/2);
|
|
g->cam.y = (float)(hy*TILE - VIEW_H/2);
|
|
if (g->cam.x < 0) g->cam.x = 0;
|
|
if (g->cam.y < 0) g->cam.y = 0;
|
|
|
|
g->state = GAME_PLAYING;
|
|
engine_log("Mission %d '%s': %s vs %s (%s)",
|
|
m->id, m->name, faction_name(g->human), faction_name(enemy),
|
|
m->diff==DIFF_EASY?"Easy":m->diff==DIFF_HARD?"Hard":"Normal");
|
|
}
|
|
|
|
void engine_check_win(Game *g) {
|
|
Faction human = g->human;
|
|
Faction enemy = g->enemy;
|
|
int human_base = 0, enemy_base = 0;
|
|
int human_bld = 0, enemy_bld = 0;
|
|
for (int i = 0; i < g->bld_count; i++) {
|
|
Building *b = &g->buildings[i];
|
|
if (b->dead) continue;
|
|
if (b->faction == human) { human_bld++; if (b->type==BT_BASE) human_base=1; }
|
|
if (b->faction == enemy) { enemy_bld++; if (b->type==BT_BASE) enemy_base=1; }
|
|
}
|
|
if (!human_base || human_bld == 0) { g->state = GAME_LOST; g->winner = enemy; return; }
|
|
|
|
switch (g->objective) {
|
|
case OBJ_SURVIVE:
|
|
if (g->time >= ((const Mission*)mission_get(g->mission_id))->survive_time)
|
|
{ g->state = GAME_WON; g->winner = human; }
|
|
break;
|
|
case OBJ_GATHER:
|
|
if (g->scrap[human] >= ((const Mission*)mission_get(g->mission_id))->scrap_goal)
|
|
{ g->state = GAME_WON; g->winner = human; }
|
|
break;
|
|
default: /* OBJ_DESTROY_ALL */
|
|
if (!enemy_base || enemy_bld == 0) { g->state = GAME_WON; g->winner = human; }
|
|
break;
|
|
}
|
|
}
|
|
|
|
void engine_update(Game *g, float dt) {
|
|
if (g->state != GAME_PLAYING) return;
|
|
dt *= g->speed_mul;
|
|
g->time += dt;
|
|
g->frames++;
|
|
|
|
unit_update(g, dt);
|
|
bld_update(g, dt);
|
|
bullet_update(g, dt);
|
|
particle_update(g, dt);
|
|
ai_update(g, dt);
|
|
map_update_fog(g, g->human);
|
|
audio_music_tick(dt);
|
|
if (g->cam.shake > 0) { g->cam.shake -= dt * 24.0f; if (g->cam.shake < 0) g->cam.shake = 0; }
|
|
engine_check_win(g);
|
|
}
|