kitchen: README + MISSIONS/engine-c keep-drop notes
This commit is contained in:
parent
1d351918da
commit
575d7d29b8
130 changed files with 9817 additions and 1 deletions
407
engine-c/src/map.c
Normal file
407
engine-c/src/map.c
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
#include "kknd.h"
|
||||
|
||||
/* =========================================================================
|
||||
* map.c - Wasteland terrain generation and A* pathfinding.
|
||||
* ========================================================================= */
|
||||
|
||||
static Tile *g_tiles = NULL; /* owned by GameMap but accessed via map_tile */
|
||||
|
||||
void map_init(GameMap *m, RNG *rng) {
|
||||
m->w = MAP_W;
|
||||
m->h = MAP_H;
|
||||
m->tiles = (Tile *)calloc((size_t)m->w * m->h, sizeof(Tile));
|
||||
g_tiles = m->tiles;
|
||||
m->fog = (uint8_t *)calloc((size_t)m->w * m->h, 1);
|
||||
m->scrap_fields = 0;
|
||||
|
||||
/* base terrain: wasteland with scattered rubble */
|
||||
for (int y = 0; y < m->h; y++) {
|
||||
for (int x = 0; x < m->w; x++) {
|
||||
Tile *t = &m->tiles[y * m->w + x];
|
||||
t->terrain = T_WASTELAND;
|
||||
t->variant = (uint8_t)rng_range(rng, 0, 7);
|
||||
t->scrap = 0;
|
||||
t->occupied = 0;
|
||||
t->walkable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* terrain theme knobs */
|
||||
int rubble_n = 40, grass_n = 14, water_n = 8;
|
||||
if (m->style == 1) { rubble_n = 22; grass_n = 20; water_n = 4; } /* open */
|
||||
else if (m->style == 2) { rubble_n = 30; grass_n = 10; water_n = 16; } /* islands */
|
||||
else if (m->style == 3) { rubble_n = 55; grass_n = 8; water_n = 8; } /* fortress */
|
||||
|
||||
/* rubble patches */
|
||||
for (int i = 0; i < rubble_n; i++) {
|
||||
int cx = rng_range(rng, 4, m->w - 5);
|
||||
int cy = rng_range(rng, 4, m->h - 5);
|
||||
int r = rng_range(rng, 3, 9);
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(m, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||
Tile *t = &m->tiles[y*m->w + x];
|
||||
if (rng_f32(rng) < 0.7f) t->terrain = T_RUBBLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* grass oases */
|
||||
for (int i = 0; i < grass_n; i++) {
|
||||
int cx = rng_range(rng, 4, m->w - 5);
|
||||
int cy = rng_range(rng, 4, m->h - 5);
|
||||
int r = rng_range(rng, 3, 7);
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(m, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r)
|
||||
m->tiles[y*m->w + x].terrain = T_GRASS;
|
||||
}
|
||||
}
|
||||
|
||||
/* roads: a few straight-ish lines */
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int horiz = rng_range(rng, 0, 1);
|
||||
int pos = rng_range(rng, 8, m->w - 9);
|
||||
int len = rng_range(rng, 20, 60);
|
||||
int x = rng_range(rng, 2, m->w - 3);
|
||||
int y = rng_range(rng, 2, m->h - 3);
|
||||
for (int s = 0; s < len; s++) {
|
||||
if (map_in_bounds(m, x, y)) m->tiles[y*m->w + x].terrain = T_ROAD;
|
||||
if (horiz) x += (rng_f32(rng) < 0.85f) ? 1 : 0, y += rng_range(rng,-1,1);
|
||||
else y += (rng_f32(rng) < 0.85f) ? 1 : 0, x += rng_range(rng,-1,1);
|
||||
}
|
||||
}
|
||||
|
||||
/* water lakes (blocked) */
|
||||
for (int i = 0; i < water_n; i++) {
|
||||
int cx = rng_range(rng, 6, m->w - 7);
|
||||
int cy = rng_range(rng, 6, m->h - 7);
|
||||
int r = rng_range(rng, 3, 8);
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(m, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||
Tile *t = &m->tiles[y*m->w + x];
|
||||
t->terrain = T_WATER;
|
||||
t->walkable = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* rock mountains (blocked) */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int cx = rng_range(rng, 6, m->w - 7);
|
||||
int cy = rng_range(rng, 6, m->h - 7);
|
||||
int r = rng_range(rng, 3, 9);
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(m, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||
Tile *t = &m->tiles[y*m->w + x];
|
||||
if (t->terrain != T_WATER) {
|
||||
t->terrain = T_ROCK;
|
||||
t->walkable = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* scrap fields (resource) scattered, avoid spawn corners */
|
||||
int attempts = 0;
|
||||
while (m->scrap_fields < 22 && attempts < 4000) {
|
||||
attempts++;
|
||||
int cx = rng_range(rng, 6, m->w - 7);
|
||||
int cy = rng_range(rng, 6, m->h - 7);
|
||||
int r = rng_range(rng, 2, 4);
|
||||
if (kknd_dist2((float)cx,(float)cy, 10, m->h-10) < 20*20) continue;
|
||||
if (kknd_dist2((float)cx,(float)cy, m->w-10, 10) < 20*20) continue;
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(m, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||
Tile *t = &m->tiles[y*m->w + x];
|
||||
if (t->walkable && t->terrain != T_WATER) {
|
||||
t->terrain = T_SCRAP;
|
||||
t->scrap = 255;
|
||||
m->scrap_fields++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* spawn points: Survivor bottom-left, Evolved top-right */
|
||||
m->spawn_x[FACTION_SURVIVOR] = 10;
|
||||
m->spawn_y[FACTION_SURVIVOR] = m->h - 10;
|
||||
m->spawn_x[FACTION_EVOLVED] = m->w - 10;
|
||||
m->spawn_y[FACTION_EVOLVED] = 10;
|
||||
m->spawn_x[FACTION_SERIES9] = 10;
|
||||
m->spawn_y[FACTION_SERIES9] = 10;
|
||||
|
||||
/* clear spawn areas to wasteland & walkable */
|
||||
for (int f = 1; f <= 2; f++) {
|
||||
int sx = m->spawn_x[f], sy = m->spawn_y[f];
|
||||
for (int y = sy - 8; y <= sy + 8; y++)
|
||||
for (int x = sx - 8; x <= sx + 8; x++) {
|
||||
if (!map_in_bounds(m, x, y)) continue;
|
||||
Tile *t = &m->tiles[y*m->w + x];
|
||||
t->terrain = T_WASTELAND;
|
||||
t->walkable = 1;
|
||||
t->occupied = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void map_free(GameMap *m) {
|
||||
free(m->tiles);
|
||||
free(m->fog);
|
||||
m->tiles = NULL;
|
||||
m->fog = NULL;
|
||||
g_tiles = NULL;
|
||||
}
|
||||
|
||||
void map_update_fog(Game *g, Faction f) {
|
||||
uint8_t *vis = g->map.fog;
|
||||
int n = g->map.w * g->map.h;
|
||||
for (int i = 0; i < n; i++) if (vis[i] == 2) vis[i] = 1;
|
||||
|
||||
/* reveal around human units */
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead || u->faction != f) continue;
|
||||
int cx = (int)(u->x / TILE), cy = (int)(u->y / TILE);
|
||||
int r = (int)(u->sight / TILE) + 1;
|
||||
if (r < 4) r = 4;
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(&g->map, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||
int idx = y*g->map.w + x;
|
||||
vis[idx] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* reveal around human buildings */
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead || b->faction != f) continue;
|
||||
int cx = b->tx + b->w/2, cy = b->ty + b->h/2;
|
||||
int r = 8 + b->w;
|
||||
for (int y = cy - r; y <= cy + r; y++)
|
||||
for (int x = cx - r; x <= cx + r; x++) {
|
||||
if (!map_in_bounds(&g->map, x, y)) continue;
|
||||
if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) {
|
||||
int idx = y*g->map.w + x;
|
||||
vis[idx] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tile *map_tile(GameMap *m, int tx, int ty) {
|
||||
if (!map_in_bounds(m, tx, ty)) return NULL;
|
||||
return &m->tiles[ty * m->w + tx];
|
||||
}
|
||||
|
||||
int map_in_bounds(GameMap *m, int tx, int ty) {
|
||||
return tx >= 0 && ty >= 0 && tx < m->w && ty < m->h;
|
||||
}
|
||||
|
||||
int map_walkable(GameMap *m, int tx, int ty) {
|
||||
if (!map_in_bounds(m, tx, ty)) return 0;
|
||||
Tile *t = &m->tiles[ty * m->w + tx];
|
||||
return t->walkable && !t->occupied;
|
||||
}
|
||||
|
||||
void map_world_to_tile(float wx, float wy, int *tx, int *ty) {
|
||||
*tx = (int)(wx / TILE);
|
||||
*ty = (int)(wy / TILE);
|
||||
}
|
||||
|
||||
void map_tile_to_world(int tx, int ty, float *wx, float *wy) {
|
||||
*wx = (float)(tx * TILE + TILE / 2);
|
||||
*wy = (float)(ty * TILE + TILE / 2);
|
||||
}
|
||||
|
||||
int map_find_nearest_scrap(GameMap *m, float wx, float wy, float *outx, float *outy) {
|
||||
int best_tx = -1, best_ty = -1;
|
||||
float best = 1e18f;
|
||||
for (int y = 0; y < m->h; y++) {
|
||||
for (int x = 0; x < m->w; x++) {
|
||||
Tile *t = &m->tiles[y*m->w + x];
|
||||
if (t->terrain == T_SCRAP && t->scrap > 0) {
|
||||
float dx = (float)(x*TILE) - wx, dy = (float)(y*TILE) - wy;
|
||||
float d = dx*dx + dy*dy;
|
||||
if (d < best) { best = d; best_tx = x; best_ty = y; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_tx < 0) return 0;
|
||||
map_tile_to_world(best_tx, best_ty, outx, outy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void map_deplete_scrap(GameMap *m, int tx, int ty, int amount) {
|
||||
Tile *t = map_tile(m, tx, ty);
|
||||
if (!t || t->terrain != T_SCRAP) return;
|
||||
int s = (int)t->scrap - amount;
|
||||
if (s <= 0) {
|
||||
t->scrap = 0;
|
||||
t->terrain = T_RUBBLE; /* depleted field becomes rubble */
|
||||
} else {
|
||||
t->scrap = (uint8_t)s;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- A* ----------------------------- */
|
||||
/* Binary heap of tile indices keyed by f-score in a parallel array. */
|
||||
#define ASTAR_NODES (MAP_W * MAP_H)
|
||||
|
||||
typedef struct {
|
||||
int g[ASTAR_NODES];
|
||||
int f[ASTAR_NODES];
|
||||
int came[ASTAR_NODES];
|
||||
int closed[ASTAR_NODES];
|
||||
int open[ASTAR_NODES];
|
||||
int heap[ASTAR_NODES];
|
||||
int heap_n;
|
||||
} AStar;
|
||||
|
||||
static AStar *g_astar = NULL;
|
||||
|
||||
static void heap_push(AStar *a, int idx) {
|
||||
int i = a->heap_n++;
|
||||
a->heap[i] = idx;
|
||||
while (i > 0) {
|
||||
int p = (i - 1) / 2;
|
||||
if (a->f[a->heap[p]] <= a->f[a->heap[i]]) break;
|
||||
int tmp = a->heap[p]; a->heap[p] = a->heap[i]; a->heap[i] = tmp;
|
||||
i = p;
|
||||
}
|
||||
}
|
||||
|
||||
static int heap_pop(AStar *a) {
|
||||
int top = a->heap[0];
|
||||
a->heap[0] = a->heap[--a->heap_n];
|
||||
int i = 0;
|
||||
for (;;) {
|
||||
int l = 2*i+1, r = 2*i+2, m = i;
|
||||
if (l < a->heap_n && a->f[a->heap[l]] < a->f[a->heap[m]]) m = l;
|
||||
if (r < a->heap_n && a->f[a->heap[r]] < a->f[a->heap[m]]) m = r;
|
||||
if (m == i) break;
|
||||
int tmp = a->heap[m]; a->heap[m] = a->heap[i]; a->heap[i] = tmp;
|
||||
i = m;
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
static int heuristic(int ax, int ay, int bx, int by) {
|
||||
int dx = abs(ax - bx), dy = abs(ay - by);
|
||||
return (dx + dy) + (14 - 20) * (dx < dy ? dx : dy); /* octile-ish */
|
||||
}
|
||||
|
||||
static const int NX[8] = { 1,-1,0,0,1,1,-1,-1 };
|
||||
static const int NY[8] = { 0,0,1,-1,1,-1,1,-1 };
|
||||
|
||||
int path_find(GameMap *m, int sx, int sy, int gx, int gy, Path *out) {
|
||||
out->len = 0;
|
||||
if (!map_in_bounds(m, sx, sy) || !map_in_bounds(m, gx, gy)) return 0;
|
||||
|
||||
/* if goal blocked, retarget to nearest walkable neighbour */
|
||||
if (!map_walkable(m, gx, gy)) {
|
||||
int best = -1, bestd = 1e9;
|
||||
for (int d = 0; d < 8; d++) {
|
||||
int nx = gx + NX[d], ny = gy + NY[d];
|
||||
if (map_walkable(m, nx, ny)) {
|
||||
int dist = abs(nx-sx)+abs(ny-sy);
|
||||
if (dist < bestd) { bestd = dist; best = ny*m->w+nx; }
|
||||
}
|
||||
}
|
||||
if (best < 0) return 0;
|
||||
gx = best % m->w; gy = best / m->w;
|
||||
}
|
||||
|
||||
if (g_astar == NULL) g_astar = (AStar *)calloc(1, sizeof(AStar));
|
||||
AStar *a = g_astar;
|
||||
for (int i = 0; i < m->w*m->h; i++) { a->closed[i]=0; a->came[i]=-1; a->g[i]=1e9; }
|
||||
a->heap_n = 0;
|
||||
|
||||
int start = sy*m->w + sx;
|
||||
int goal = gy*m->w + gx;
|
||||
a->g[start] = 0;
|
||||
a->f[start] = heuristic(sx, sy, gx, gy);
|
||||
heap_push(a, start);
|
||||
a->open[start] = 1;
|
||||
|
||||
int found = 0;
|
||||
int iter = 0;
|
||||
while (a->heap_n > 0 && iter++ < ASTAR_NODES * 4) {
|
||||
int cur = heap_pop(a);
|
||||
if (a->closed[cur]) continue;
|
||||
a->closed[cur] = 1;
|
||||
if (cur == goal) { found = 1; break; }
|
||||
int cx = cur % m->w, cy = cur / m->w;
|
||||
for (int d = 0; d < 8; d++) {
|
||||
int nx = cx + NX[d], ny = cy + NY[d];
|
||||
if (!map_in_bounds(m, nx, ny)) continue;
|
||||
int ni = ny*m->w + nx;
|
||||
if (a->closed[ni]) continue;
|
||||
int step = (d < 4) ? 10 : 14;
|
||||
if (!map_walkable(m, nx, ny) && ni != goal) continue;
|
||||
int ng = a->g[cur] + step;
|
||||
if (ng < a->g[ni]) {
|
||||
a->g[ni] = ng;
|
||||
a->came[ni] = cur;
|
||||
a->f[ni] = ng + heuristic(nx, ny, gx, gy);
|
||||
heap_push(a, ni);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) return 0;
|
||||
|
||||
/* reconstruct */
|
||||
int buf[ASTAR_NODES];
|
||||
int n = 0;
|
||||
for (int c = goal; c != -1; c = a->came[c]) buf[n++] = c;
|
||||
/* reverse */
|
||||
int len = 0;
|
||||
for (int i = n - 1; i >= 0 && len < MAX_PATH; i--) {
|
||||
out->nodes[len].tx = buf[i] % m->w;
|
||||
out->nodes[len].ty = buf[i] / m->w;
|
||||
len++;
|
||||
}
|
||||
out->len = len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void path_smooth(GameMap *m, Path *p) {
|
||||
if (p->len < 3) return;
|
||||
/* simple line-of-sight smoothing: skip waypoints when straight clear */
|
||||
int out_i = 0;
|
||||
int i = 0;
|
||||
while (i < p->len - 1) {
|
||||
int j = p->len - 1;
|
||||
while (j > i + 1) {
|
||||
/* check tiles between i and j */
|
||||
int x0 = p->nodes[i].tx, y0 = p->nodes[i].ty;
|
||||
int x1 = p->nodes[j].tx, y1 = p->nodes[j].ty;
|
||||
int steps = abs(x1-x0) > abs(y1-y0) ? abs(x1-x0) : abs(y1-y0);
|
||||
int ok = 1;
|
||||
for (int s = 1; s < steps; s++) {
|
||||
int tx = x0 + (x1-x0)*s/steps;
|
||||
int ty = y0 + (y1-y0)*s/steps;
|
||||
if (!map_walkable(m, tx, ty)) { ok = 0; break; }
|
||||
}
|
||||
if (ok) break;
|
||||
j--;
|
||||
}
|
||||
p->nodes[out_i] = p->nodes[i];
|
||||
out_i++;
|
||||
i = j;
|
||||
}
|
||||
p->nodes[out_i] = p->nodes[p->len-1];
|
||||
out_i++;
|
||||
p->len = out_i;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue