#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. */ 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; } } } } } 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); /* 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 (sim may add more) */ 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 f = 0.35f + 0.45f * ((i * 17) % 100) / 100.0f; u->hp = u->max_hp * f; } /* FX seeds at the clash locus */ 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); /* point raid at nearest human combat units / HQ */ 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; 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; } 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); } /* human combat units engage nearest raiders (spread targets) */ 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; int tgt = -1; for (int k = 0; k < rn; k++) { int ri = raid[(ridx + k) % rn]; 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 */ unit_clear_selection(g); int nsel = 0; for (int i = 0; i < g->unit_count && nsel < 8; 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; unit_select_single(g, i, 1); nsel++; } } 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->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); }