#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 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, (f==FACTION_SURVIVOR?UT_INFANTRY:UT_INFANTRY), f, wx - 40 + i*20, wy - 20); /* a couple of buggies */ for (int i = 0; i < 2; i++) unit_spawn(g, (f==FACTION_SURVIVOR?UT_BUGGY:UT_BUGGY), f, wx - 30 + i*30, wy - 40); } 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); /* 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); } } /* 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); }