#include "kknd.h" /* ========================================================================= * buildings.c - Structures, placement, production and defensive fire. * ========================================================================= */ Building *bld_by_id(Game *g, int id) { for (int i = 0; i < g->bld_count; i++) if (!g->buildings[i].dead && g->buildings[i].id == id) return &g->buildings[i]; return NULL; } int bld_can_place(Game *g, BuildingType t, int tx, int ty) { const BldDef *d = bld_def(t); for (int y = ty; y < ty + d->h; y++) for (int x = tx; x < tx + d->w; x++) { if (!map_in_bounds(&g->map, x, y)) return 0; Tile *tl = map_tile(&g->map, x, y); if (!tl->walkable || tl->occupied) return 0; if (tl->terrain == T_WATER || tl->terrain == T_ROCK) return 0; } return 1; } int bld_spawn(Game *g, BuildingType t, Faction f, int tx, int ty) { if (g->bld_count >= MAX_BUILDINGS) return -1; if (!bld_can_place(g, t, tx, ty)) return -2; const BldDef *d = bld_def(t); Building *b = &g->buildings[g->bld_count++]; memset(b, 0, sizeof(Building)); b->id = g->bld_next_id++; b->type = t; b->faction = f; b->tx = tx; b->ty = ty; b->w = d->w; b->h = d->h; b->max_hp = (float)d->hp; b->build = 0; /* starts under construction */ b->hp = (float)d->hp * 0.1f; b->producing = 0; b->prod_timer = 0; b->prod_total = 0; b->queue_len = 0; b->rally_x = (float)((tx + d->w/2) * TILE + TILE*2); b->rally_y = (float)((ty + d->h/2) * TILE); b->selected = 0; b->dead = 0; b->atk_cd = 0; for (int y = ty; y < ty + d->h; y++) for (int x = tx; x < tx + d->w; x++) map_tile(&g->map, x, y)->occupied = 1; return g->bld_count - 1; } void bld_remove(Game *g, int idx) { if (idx < 0 || idx >= g->bld_count) return; Building *b = &g->buildings[idx]; for (int y = b->ty; y < b->ty + b->h; y++) for (int x = b->tx; x < b->tx + b->w; x++) { Tile *t = map_tile(&g->map, x, y); if (t) t->occupied = 0; } if (b->selected) { for (int i = 0; i < g->sel_count; i++) if (g->selection[i] == b->id) { g->selection[i] = g->selection[g->sel_count-1]; g->sel_count--; i--; } } g->buildings[idx] = g->buildings[g->bld_count - 1]; g->bld_count--; } void bld_apply_damage(Game *g, Building *b, float dmg) { float eff = dmg - 3.0f; if (eff < 1) eff = 1; b->hp -= eff; particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2), (float)(b->ty*TILE+b->h*TILE/2), 2, 2, 255, 200, 80); if (b->hp <= 0 && !b->dead) { b->dead = 1; float cx = (float)(b->tx*TILE+b->w*TILE/2); float cy = (float)(b->ty*TILE+b->h*TILE/2); particle_burst(g, cx, cy, 28, 1, 255, 140, 40); particle_burst(g, cx, cy, 16, 3, 90, 80, 70); particle_burst(g, cx + 10, cy - 8, 10, 0, 100, 90, 80); particle_burst(g, cx - 8, cy + 10, 8, 1, 55, 45, 40); /* scorch */ audio_sfx(2); if (g->cam.shake < 9) g->cam.shake = 9; if (b->faction == g->human) game_log(g, "Structure lost: %s", bld_def(b->type)->name); /* lasting rubble under footprint; occupied cleared later by bld_remove */ map_scar_bld_death(&g->map, b->tx, b->ty, b->w, b->h); } } void bld_queue_unit(Game *g, int idx, UnitType t) { Building *b = &g->buildings[idx]; const BldDef *d = bld_def(b->type); int allowed = 0; for (int i = 0; i < d->produces_count; i++) if (d->produces[i] == t) { allowed = 1; break; } if (!allowed) { audio_sfx(5); return; } if (b->queue_len >= MAX_SELECTION) { audio_sfx(5); return; } const UnitDef *ud = unit_def(t, b->faction); if (g->scrap[b->faction] < ud->cost) { audio_sfx(5); return; } b->queue[b->queue_len++] = t; g->scrap[b->faction] -= ud->cost; audio_sfx(3); } void bld_update(Game *g, float dt) { for (int f = 0; f < MAX_FACTIONS; f++) g->research_lab[f] = 0; for (int i = 0; i < g->bld_count; i++) { Building *b = &g->buildings[i]; if (b->dead) continue; const BldDef *d = bld_def(b->type); if (d->is_research && b->build >= 1.0f) g->research_lab[b->faction]++; /* construction progress (faster with Logistics upgrade) */ if (b->build < 1.0f) { float rate = (float)d->build_time; rate /= (1.0f + 0.12f * (float)g->upg[b->faction].lvl[UP_BUILD]); b->build += dt / rate; if (b->build >= 1.0f) { b->build = 1.0f; b->hp = b->max_hp; audio_sfx(3); } else b->hp = b->max_hp * (0.1f + 0.9f * b->build); } if (b->atk_cd > 0) b->atk_cd -= dt; /* turret auto-fire */ if (d->is_turret && b->build >= 1.0f && b->atk_cd <= 0) { float bx = (float)(b->tx*TILE + b->w*TILE/2); float by = (float)(b->ty*TILE + b->h*TILE/2); int best = -1; float bestd = d->range * d->range; for (int j = 0; j < g->unit_count; j++) { Unit *u = &g->units[j]; if (u->dead || u->faction == b->faction) continue; float dd = kknd_dist2(bx, by, u->x, u->y); if (dd < bestd) { bestd = dd; best = u->id; } } if (best >= 0) { bullet_spawn(g, bx, by, best, -1, d->atk, b->faction, 2); b->atk_cd = 0.6f; audio_sfx(1); } } /* production (faster with Logistics upgrade) */ if (b->build >= 1.0f && b->producing == 0 && b->queue_len > 0) { UnitType t = (UnitType)b->queue[0]; const UnitDef *ud = unit_def(t, b->faction); b->producing = t; b->prod_total = (float)ud->build_time; b->prod_timer = 0; for (int k = 0; k < b->queue_len - 1; k++) b->queue[k] = b->queue[k+1]; b->queue_len--; } if (b->producing != 0) { const UnitDef *ud = unit_def((UnitType)b->producing, b->faction); float total = (float)ud->build_time; total /= (1.0f + 0.12f * (float)g->upg[b->faction].lvl[UP_BUILD]); b->prod_timer += dt; if (b->prod_timer >= total) { unit_spawn(g, (UnitType)b->producing, b->faction, b->rally_x, b->rally_y); b->producing = 0; b->prod_timer = 0; audio_sfx(3); } } /* repair depot heals nearby units */ if (d->type == BT_REPAIR && b->build >= 1.0f) { float bx = (float)(b->tx*TILE + b->w*TILE/2); float by = (float)(b->ty*TILE + b->h*TILE/2); for (int j = 0; j < g->unit_count; j++) { Unit *u = &g->units[j]; if (u->dead || u->faction != b->faction) continue; if (kknd_dist2(bx, by, u->x, u->y) < (TILE*5)*(TILE*5) && u->hp < u->max_hp) { u->hp += dt * 15.0f; if (u->hp > u->max_hp) u->hp = u->max_hp; } } } } /* research progression */ for (int f = 0; f < MAX_FACTIONS; f++) { if (g->research_lab[f] <= 0) { g->research_cur[f] = -1; g->research_prog[f] = 0; continue; } int up = g->research_cur[f]; if (up < 0) continue; int lvl = g->upg[f].lvl[up]; if (lvl >= UP_MAX_LVL) { g->research_cur[f] = -1; g->research_prog[f] = 0; continue; } g->research_prog[f] += dt * (float)g->research_lab[f] / 18.0f; if (g->research_prog[f] >= 1.0f) { g->upg[f].lvl[up] = lvl + 1; g->research_prog[f] = 0; g->research_cur[f] = -1; audio_sfx(3); engine_log("%s researched %s (lvl %d)", faction_name((Faction)f), research_name(up), lvl + 1); if ((Faction)f == g->human) game_log(g, "Research complete: %s (lvl %d)", research_name(up), lvl + 1); } } for (int i = g->bld_count - 1; i >= 0; i--) if (g->buildings[i].dead) bld_remove(g, i); } int bld_research(Game *g, Faction f, int up) { if (up < 0 || up >= UP_COUNT) return 0; if (g->research_lab[f] <= 0) return 0; if (g->research_cur[f] >= 0) return 0; if (g->upg[f].lvl[up] >= UP_MAX_LVL) return 0; int cost = research_cost(up, g->upg[f].lvl[up]); if (g->scrap[f] < cost) { audio_sfx(5); return 0; } g->scrap[f] -= cost; g->research_cur[f] = up; g->research_prog[f] = 0; audio_sfx(3); return 1; }