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
631
engine-c/src/render.c
Normal file
631
engine-c/src/render.c
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
#include "kknd.h"
|
||||
|
||||
static void render_message_log(Game *g);
|
||||
|
||||
/* =========================================================================
|
||||
* render.c - All 2D drawing: terrain, units, buildings, HUD and minimap.
|
||||
* Recreates the chunky overhead KKND battlefield presentation.
|
||||
* ========================================================================= */
|
||||
|
||||
int render_init(Game *g) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
|
||||
fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
if (TTF_Init() != 0) {
|
||||
fprintf(stderr, "TTF_Init: %s\n", TTF_GetError());
|
||||
return 0;
|
||||
}
|
||||
g->win = SDL_CreateWindow(KKND_NAME " - Krush, Kill 'n' Destroy",
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, VIEW_W, VIEW_H,
|
||||
SDL_WINDOW_SHOWN);
|
||||
if (!g->win) return 0;
|
||||
g->ren = SDL_CreateRenderer(g->win, -1,
|
||||
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if (!g->ren) return 0;
|
||||
SDL_SetRenderDrawBlendMode(g->ren, SDL_BLENDMODE_BLEND);
|
||||
sprites_init(&g->spr, g->ren);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void render_text(Sprites *s, SDL_Renderer *r, TTF_Font *f,
|
||||
const char *txt, int x, int y, SDL_Color c) {
|
||||
if (!f || !txt || !*txt) return;
|
||||
SDL_Surface *surf = TTF_RenderText_Blended(f, txt, c);
|
||||
if (!surf) return;
|
||||
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, surf);
|
||||
if (tex) {
|
||||
SDL_Rect dr = { x, y, surf->w, surf->h };
|
||||
SDL_RenderCopy(r, tex, NULL, &dr);
|
||||
SDL_DestroyTexture(tex);
|
||||
}
|
||||
SDL_FreeSurface(surf);
|
||||
}
|
||||
|
||||
static void draw_tex_world(Game *g, SDL_Texture *tex, float wx, float wy,
|
||||
int w, int h, float angle, Uint8 alpha) {
|
||||
SDL_Rect r;
|
||||
r.w = w; r.h = h;
|
||||
r.x = (int)(wx - w/2 - g->cam.x);
|
||||
r.y = (int)(wy - h/2 - g->cam.y);
|
||||
SDL_SetTextureAlphaMod(tex, alpha);
|
||||
SDL_RenderCopyEx(g->ren, tex, NULL, &r, angle * 180.0f / 3.14159f,
|
||||
NULL, SDL_FLIP_NONE);
|
||||
SDL_SetTextureAlphaMod(tex, 255);
|
||||
}
|
||||
|
||||
void render_world(Game *g) {
|
||||
int tx0 = (int)(g->cam.x / TILE) - 1;
|
||||
int ty0 = (int)(g->cam.y / TILE) - 1;
|
||||
int tx1 = (int)((g->cam.x + VIEW_W) / TILE) + 1;
|
||||
int ty1 = (int)((g->cam.y + VIEW_H) / TILE) + 1;
|
||||
for (int ty = ty0; ty <= ty1; ty++) {
|
||||
for (int tx = tx0; tx <= tx1; tx++) {
|
||||
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
||||
uint8_t fv = g->map.fog[ty*g->map.w + tx];
|
||||
if (fv == 0) continue; /* unexplored: leave black */
|
||||
Tile *t = map_tile(&g->map, tx, ty);
|
||||
SDL_Rect r = { (int)(tx*TILE - g->cam.x), (int)(ty*TILE - g->cam.y), TILE, TILE };
|
||||
SDL_RenderCopy(g->ren, g->spr.terrain[t->terrain], NULL, &r);
|
||||
if (fv == 1) {
|
||||
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 120);
|
||||
SDL_RenderFillRect(g->ren, &r);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* subtle grid for the tactical look (only on explored tiles) */
|
||||
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 22);
|
||||
for (int tx = tx0; tx <= tx1; tx++) {
|
||||
int x = (int)(tx*TILE - g->cam.x);
|
||||
SDL_RenderDrawLine(g->ren, x, (int)(ty0*TILE-g->cam.y), x, (int)(ty1*TILE-g->cam.y));
|
||||
}
|
||||
for (int ty = ty0; ty <= ty1; ty++) {
|
||||
int y = (int)(ty*TILE - g->cam.y);
|
||||
SDL_RenderDrawLine(g->ren, (int)(tx0*TILE-g->cam.x), y, (int)(tx1*TILE-g->cam.x), y);
|
||||
}
|
||||
}
|
||||
|
||||
void render_buildings(Game *g) {
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead) continue;
|
||||
if (b->faction != g->human) {
|
||||
int tx = b->tx + b->w/2, ty = b->ty + b->h/2;
|
||||
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*g->map.w + tx] != 2) continue;
|
||||
}
|
||||
int W = b->w * TILE, H = b->h * TILE;
|
||||
int sx = (int)(b->tx*TILE - g->cam.x);
|
||||
int sy = (int)(b->ty*TILE - g->cam.y);
|
||||
SDL_Rect r = { sx, sy, W, H };
|
||||
SDL_Texture *tex = g->spr.bld[b->type][b->faction];
|
||||
if (b->build < 1.0f) {
|
||||
/* construction: draw then scaffold overlay */
|
||||
SDL_SetTextureAlphaMod(tex, 160);
|
||||
SDL_RenderCopy(g->ren, tex, NULL, &r);
|
||||
SDL_SetTextureAlphaMod(tex, 255);
|
||||
int pct = (int)(b->build * 100);
|
||||
SDL_Rect bar = { sx, sy - 8, W, 5 };
|
||||
SDL_SetRenderDrawColor(g->ren, 200, 200, 200, 255);
|
||||
SDL_RenderFillRect(g->ren, &bar);
|
||||
SDL_Rect fill = { sx, sy - 8, (int)(W * b->build), 5 };
|
||||
SDL_SetRenderDrawColor(g->ren, 120, 220, 120, 255);
|
||||
SDL_RenderFillRect(g->ren, &fill);
|
||||
} else {
|
||||
SDL_RenderCopy(g->ren, tex, NULL, &r);
|
||||
}
|
||||
/* health bar if damaged or selected */
|
||||
if (b->hp < b->max_hp || b->selected) {
|
||||
SDL_Rect bar = { sx, sy - 6, W, 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, 60, 60, 60, 255);
|
||||
SDL_RenderFillRect(g->ren, &bar);
|
||||
float f = b->hp / b->max_hp;
|
||||
SDL_Color c = (b->faction == FACTION_SURVIVOR) ? (SDL_Color){90,160,70,255}
|
||||
: (SDL_Color){170,70,180,255};
|
||||
SDL_Rect fill = { sx, sy - 6, (int)(W * f), 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||
SDL_RenderFillRect(g->ren, &fill);
|
||||
}
|
||||
if (b->selected) {
|
||||
SDL_SetRenderDrawColor(g->ren, 255, 255, 255, 200);
|
||||
SDL_RenderDrawRect(g->ren, &r);
|
||||
}
|
||||
/* production progress */
|
||||
if (b->producing != 0) {
|
||||
const UnitDef *ud = unit_def((UnitType)b->producing, b->faction);
|
||||
float f = b->prod_timer / (float)ud->build_time;
|
||||
SDL_Rect bar = { sx, sy + H + 2, W, 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, 60, 60, 60, 255);
|
||||
SDL_RenderFillRect(g->ren, &bar);
|
||||
SDL_Rect fill = { sx, sy + H + 2, (int)(W * f), 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, 255, 220, 120, 255);
|
||||
SDL_RenderFillRect(g->ren, &fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render_wrecks(Game *g) {
|
||||
for (int i = 0; i < g->wreck_count; i++) {
|
||||
Wreck *w = &g->wrecks[i];
|
||||
int tx = (int)(w->x / TILE), ty = (int)(w->y / TILE);
|
||||
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
||||
if (g->map.fog[ty*g->map.w + tx] == 0) continue;
|
||||
SDL_Color c = faction_color(w->faction);
|
||||
int sx = (int)(w->x - g->cam.x), sy = (int)(w->y - g->cam.y);
|
||||
SDL_SetRenderDrawColor(g->ren, c.r/2, c.g/2, c.b/2, 200);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-12, sy-9, 24, 18 });
|
||||
SDL_SetRenderDrawColor(g->ren, 30, 28, 26, 200);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-8, sy-5, 16, 10 });
|
||||
}
|
||||
}
|
||||
|
||||
void render_units(Game *g) {
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead) continue;
|
||||
if (u->faction != g->human) {
|
||||
int tx = (int)(u->x / TILE), ty = (int)(u->y / TILE);
|
||||
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*g->map.w + tx] != 2) continue;
|
||||
}
|
||||
int sz = 28;
|
||||
SDL_Texture *tex = g->spr.unit[u->type][u->faction];
|
||||
draw_tex_world(g, tex, u->x, u->y, sz, sz, u->heading, 255);
|
||||
/* selection ring */
|
||||
if (u->selected) {
|
||||
int cx = (int)(u->x - g->cam.x), cy = (int)(u->y - g->cam.y);
|
||||
SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 220);
|
||||
for (int a = 0; a < 360; a += 12) {
|
||||
int px = cx + (int)(cosf(a*3.14159f/180)*16);
|
||||
int py = cy + (int)(sinf(a*3.14159f/180)*16);
|
||||
SDL_RenderDrawPoint(g->ren, px, py);
|
||||
}
|
||||
}
|
||||
/* health bar */
|
||||
if (u->hp < u->max_hp || u->selected) {
|
||||
int cx = (int)(u->x - g->cam.x), by = (int)(u->y - g->cam.y) - 18;
|
||||
int bw = 24;
|
||||
SDL_Rect bg = { cx - bw/2, by, bw, 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, 50, 50, 50, 255);
|
||||
SDL_RenderFillRect(g->ren, &bg);
|
||||
float f = u->hp / u->max_hp;
|
||||
SDL_Color c = (u->faction == FACTION_SURVIVOR) ? (SDL_Color){90,200,90,255}
|
||||
: (SDL_Color){220,110,220,255};
|
||||
SDL_Rect fg = { cx - bw/2, by, (int)(bw * f), 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||
SDL_RenderFillRect(g->ren, &fg);
|
||||
/* cargo indicator for harvesters */
|
||||
if (u->type == UT_HARVESTER && u->cargo > 0) {
|
||||
SDL_Rect cg = { cx - bw/2, by - 4, (int)(bw * u->cargo / 1000), 2 };
|
||||
SDL_SetRenderDrawColor(g->ren, 230, 190, 70, 255);
|
||||
SDL_RenderFillRect(g->ren, &cg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render_bullets(Game *g) {
|
||||
for (int i = 0; i < g->bullet_count; i++) {
|
||||
Bullet *b = &g->bullets[i];
|
||||
int sx = (int)(b->x - g->cam.x), sy = (int)(b->y - g->cam.y);
|
||||
SDL_Texture *tex = g->spr.bullet[b->kind < 4 ? b->kind : 0];
|
||||
SDL_Rect r = { sx - 5, sy - 5, 10, 10 };
|
||||
SDL_RenderCopy(g->ren, tex, NULL, &r);
|
||||
}
|
||||
}
|
||||
|
||||
void render_particles(Game *g) {
|
||||
for (int i = 0; i < g->particle_count; i++) {
|
||||
Particle *p = &g->particles[i];
|
||||
int sx = (int)(p->x - g->cam.x), sy = (int)(p->y - g->cam.y);
|
||||
Uint8 a = (Uint8)(255 * (p->life / p->max_life));
|
||||
SDL_SetRenderDrawColor(g->ren, p->r, p->g, p->b, a);
|
||||
int sz = (p->kind == 1) ? 3 : 2;
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-sz/2, sy-sz/2, sz, sz });
|
||||
}
|
||||
}
|
||||
|
||||
void render_selection(Game *g) {
|
||||
if (g->dragging) {
|
||||
int x = g->drag_x0 < g->drag_x1 ? g->drag_x0 : g->drag_x1;
|
||||
int y = g->drag_y0 < g->drag_y1 ? g->drag_y0 : g->drag_y1;
|
||||
int w = abs(g->drag_x1 - g->drag_x0);
|
||||
int h = abs(g->drag_y1 - g->drag_y0);
|
||||
SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 120);
|
||||
SDL_RenderDrawRect(g->ren, &(SDL_Rect){x, y, w, h});
|
||||
SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 30);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){x, y, w, h});
|
||||
}
|
||||
/* placement ghost */
|
||||
if (g->placing) {
|
||||
int mx, my; SDL_GetMouseState(&mx, &my);
|
||||
float wx, wy; input_screen_to_world(g, mx, my, &wx, &wy);
|
||||
int tx, ty; map_world_to_tile(wx, wy, &tx, &ty);
|
||||
int ok = bld_can_place(g, g->place_type, tx, ty);
|
||||
const BldDef *d = bld_def(g->place_type);
|
||||
int sx = (int)(tx*TILE - g->cam.x), sy = (int)(ty*TILE - g->cam.y);
|
||||
SDL_Rect r = { sx, sy, d->w*TILE, d->h*TILE };
|
||||
SDL_SetRenderDrawColor(g->ren, ok ? 120 : 220, ok ? 220 : 80, 80, 140);
|
||||
SDL_RenderFillRect(g->ren, &r);
|
||||
SDL_SetRenderDrawColor(g->ren, ok ? 120 : 220, ok ? 220 : 80, 80, 255);
|
||||
SDL_RenderDrawRect(g->ren, &r);
|
||||
}
|
||||
}
|
||||
|
||||
void hud_layout(Game *g, HudButton *out, int *n) {
|
||||
int count = 0;
|
||||
int bx = 12, by = VIEW_H - 64, bw = 92, bh = 52, gap = 6;
|
||||
|
||||
/* always-available structures (player faction) */
|
||||
BuildingType structs[] = { BT_WARFACTORY, BT_REFINERY, BT_POWER,
|
||||
BT_RESEARCH, BT_REPAIR, BT_TURRET, BT_WALL };
|
||||
for (int i = 0; i < 7; i++) {
|
||||
const BldDef *d = bld_def(structs[i]);
|
||||
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||
out[count].kind = 0; out[count].id = structs[i];
|
||||
out[count].label = d->name; out[count].cost = d->cost;
|
||||
out[count].enabled = (g->scrap[g->human] >= d->cost);
|
||||
bx += bw + gap; count++;
|
||||
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||
}
|
||||
|
||||
/* if a production building is selected, show its units */
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead || !b->selected || b->faction != g->human) continue;
|
||||
const BldDef *d = bld_def(b->type);
|
||||
for (int k = 0; k < d->produces_count; k++) {
|
||||
const UnitDef *ud = unit_def((UnitType)d->produces[k], g->human);
|
||||
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||
out[count].kind = 1; out[count].id = d->produces[k];
|
||||
out[count].label = ud->name; out[count].cost = ud->cost;
|
||||
out[count].enabled = (g->scrap[g->human] >= ud->cost);
|
||||
bx += bw + gap; count++;
|
||||
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||
}
|
||||
}
|
||||
/* if a research lab is selected, show upgrade buttons */
|
||||
{
|
||||
int has_lab = 0;
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead || !b->selected || b->faction != g->human) continue;
|
||||
const BldDef *d = bld_def(b->type);
|
||||
if (d->is_research) { has_lab = 1; break; }
|
||||
}
|
||||
if (has_lab) {
|
||||
for (int u = 0; u < UP_COUNT; u++) {
|
||||
int lvl = g->upg[g->human].lvl[u];
|
||||
int maxed = (lvl >= UP_MAX_LVL);
|
||||
int cost = maxed ? 0 : research_cost(u, lvl);
|
||||
int busy = (g->research_cur[g->human] == u);
|
||||
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||
out[count].kind = 2; out[count].id = u;
|
||||
out[count].label = research_name(u);
|
||||
out[count].cost = cost;
|
||||
out[count].enabled = !maxed && !busy && g->scrap[g->human] >= cost;
|
||||
bx += bw + gap; count++;
|
||||
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||
}
|
||||
}
|
||||
}
|
||||
/* command buttons for the current unit selection (appended to build row) */
|
||||
{
|
||||
int sel_units = 0, sel_harv = 0;
|
||||
for (int i = 0; i < g->sel_count; i++) {
|
||||
Unit *u = unit_by_id(g, g->selection[i]);
|
||||
if (u && u->faction == g->human) { sel_units++; if (u->type == UT_HARVESTER) sel_harv++; }
|
||||
}
|
||||
if (sel_units > 0) {
|
||||
#define ADDCMD(cid, lab) do { \
|
||||
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; \
|
||||
out[count].kind = 3; out[count].id = (cid); out[count].label = (lab); \
|
||||
out[count].enabled = 1; out[count].cost = 0; count++; \
|
||||
bx += bw + gap; \
|
||||
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } \
|
||||
} while (0)
|
||||
ADDCMD(0, "Stop");
|
||||
ADDCMD(1, "Attack");
|
||||
ADDCMD(4, "Hold");
|
||||
if (sel_harv > 0) {
|
||||
ADDCMD(2, "Gather");
|
||||
ADDCMD(3, "Return");
|
||||
}
|
||||
#undef ADDCMD
|
||||
/* special-ability button (siege / heal) when applicable */
|
||||
int ab = AB_NONE;
|
||||
for (int i = 0; i < g->sel_count; i++) {
|
||||
Unit *uu = unit_by_id(g, g->selection[i]);
|
||||
if (uu && uu->faction == g->human && uu->ability != AB_NONE) { ab = uu->ability; break; }
|
||||
}
|
||||
if (ab != AB_NONE) {
|
||||
const char *lab = (ab == AB_SIEGE) ? "Siege" : "Heal";
|
||||
out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh;
|
||||
out[count].kind = 4; out[count].id = ab; out[count].label = lab;
|
||||
out[count].enabled = 1; out[count].cost = 0; count++;
|
||||
bx += bw + gap;
|
||||
if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); }
|
||||
}
|
||||
}
|
||||
}
|
||||
*n = count;
|
||||
}
|
||||
|
||||
static char g_btn_label[256][64];
|
||||
void render_build_menu(Game *g) {
|
||||
HudButton btns[64]; int n = 0;
|
||||
hud_layout(g, btns, &n);
|
||||
/* panel background (covers build bar + command row) */
|
||||
SDL_SetRenderDrawColor(g->ren, 20, 20, 24, 230);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){0, VIEW_H - 138, VIEW_W, 138});
|
||||
SDL_SetRenderDrawColor(g->ren, 90, 160, 70, 255);
|
||||
SDL_RenderDrawLine(g->ren, 0, VIEW_H - 138, VIEW_W, VIEW_H - 138);
|
||||
SDL_SetRenderDrawColor(g->ren, 60, 90, 50, 255);
|
||||
SDL_RenderDrawLine(g->ren, 0, VIEW_H - 72, VIEW_W, VIEW_H - 72);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
SDL_Rect r = { btns[i].x, btns[i].y, btns[i].w, btns[i].h };
|
||||
SDL_SetRenderDrawColor(g->ren, btns[i].enabled ? 50 : 35,
|
||||
btns[i].enabled ? 70 : 40,
|
||||
btns[i].enabled ? 55 : 38, 255);
|
||||
SDL_RenderFillRect(g->ren, &r);
|
||||
SDL_SetRenderDrawColor(g->ren, btns[i].enabled ? 120 : 70,
|
||||
btns[i].enabled ? 200 : 90,
|
||||
btns[i].enabled ? 120 : 70, 255);
|
||||
SDL_RenderDrawRect(g->ren, &r);
|
||||
SDL_Color txt = btns[i].enabled ? (SDL_Color){230,230,230,255}
|
||||
: (SDL_Color){120,120,120,255};
|
||||
snprintf(g_btn_label[i], 64, "%s", btns[i].label);
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, g_btn_label[i],
|
||||
btns[i].x + 4, btns[i].y + 4, txt);
|
||||
char cost[32]; snprintf(cost, 32, "%d scrap", btns[i].cost);
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, cost,
|
||||
btns[i].x + 4, btns[i].y + btns[i].h - 18,
|
||||
(SDL_Color){230,190,70,255});
|
||||
/* level marker for research buttons */
|
||||
if (btns[i].kind == 2) {
|
||||
int lvl = g->upg[g->human].lvl[btns[i].id];
|
||||
char lvs[32];
|
||||
if (lvl >= UP_MAX_LVL) snprintf(lvs, 32, "MAX");
|
||||
else if (g->research_cur[g->human] == btns[i].id)
|
||||
snprintf(lvs, 32, "L%d..%d%%", lvl, (int)(g->research_prog[g->human]*100));
|
||||
else snprintf(lvs, 32, "L%d", lvl);
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, lvs,
|
||||
btns[i].x + 4, btns[i].y + btns[i].h - 34,
|
||||
(SDL_Color){150,210,255,255});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render_hud(Game *g) {
|
||||
/* top resource bar */
|
||||
SDL_SetRenderDrawColor(g->ren, 16, 16, 20, 220);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){0, 0, VIEW_W, 28});
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof buf, "%s - Scrap: %d", faction_name(g->human),
|
||||
g->scrap[g->human]);
|
||||
render_text(&g->spr, g->ren, g->spr.font_med, buf, 10, 4,
|
||||
(SDL_Color){230,230,230,255});
|
||||
|
||||
/* selection summary */
|
||||
int n_units = 0, n_bld = 0;
|
||||
for (int i = 0; i < g->sel_count; i++) {
|
||||
int id = g->selection[i];
|
||||
if (unit_by_id(g, id)) n_units++;
|
||||
else if (bld_by_id(g, id)) n_bld++;
|
||||
}
|
||||
if (n_units > 0 || n_bld > 0) {
|
||||
snprintf(buf, sizeof buf, "Selected: %d units, %d buildings",
|
||||
n_units, n_bld);
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, buf,
|
||||
VIEW_W - 260, 6, (SDL_Color){200,220,200,255});
|
||||
}
|
||||
|
||||
/* hints */
|
||||
render_text(&g->spr, g->ren, g->spr.font_small,
|
||||
"LMB select/drag RMB move/attack A select-all [B] build Space pause F5 save",
|
||||
10, VIEW_H - 90, (SDL_Color){150,150,150,255});
|
||||
|
||||
/* objective */
|
||||
const Mission *m = mission_get(g->mission_id);
|
||||
const char *obj = (g->objective == OBJ_SURVIVE) ? "Survive the assault"
|
||||
: (g->objective == OBJ_GATHER) ? "Stockpile scrap"
|
||||
: "Destroy all enemies";
|
||||
char obuf[128];
|
||||
if (g->objective == OBJ_SURVIVE)
|
||||
snprintf(obuf, sizeof obuf, "Objective: %s (%.0f/%.0f s)", obj,
|
||||
g->time, m->survive_time);
|
||||
else if (g->objective == OBJ_GATHER)
|
||||
snprintf(obuf, sizeof obuf, "Objective: %s (%d/%d)", obj,
|
||||
g->scrap[g->human], m->scrap_goal);
|
||||
else
|
||||
snprintf(obuf, sizeof obuf, "Objective: %s", obj);
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, obuf,
|
||||
VIEW_W/2 - 90, 6, (SDL_Color){230,230,160,255});
|
||||
|
||||
if (g->state == GAME_PAUSED)
|
||||
render_text(&g->spr, g->ren, g->spr.font_big, "PAUSED",
|
||||
VIEW_W/2 - 70, VIEW_H/2 - 20, (SDL_Color){255,255,255,255});
|
||||
if (g->state == GAME_WON)
|
||||
render_text(&g->spr, g->ren, g->spr.font_big, "VICTORY",
|
||||
VIEW_W/2 - 80, VIEW_H/2 - 20, (SDL_Color){120,255,120,255});
|
||||
if (g->state == GAME_LOST)
|
||||
render_text(&g->spr, g->ren, g->spr.font_big, "DEFEAT",
|
||||
VIEW_W/2 - 70, VIEW_H/2 - 20, (SDL_Color){255,120,120,255});
|
||||
if (g->state == GAME_WON || g->state == GAME_LOST)
|
||||
render_text(&g->spr, g->ren, g->spr.font_med, "Press M for mission menu",
|
||||
VIEW_W/2 - 120, VIEW_H/2 + 20, (SDL_Color){220,220,220,255});
|
||||
}
|
||||
|
||||
void render_minimap(Game *g) {
|
||||
int mw = 200, mh = 200;
|
||||
int mx = VIEW_W - mw - 10, my = 36;
|
||||
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 220);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){mx-2, my-2, mw+4, mh+4});
|
||||
float sx = (float)mw / MAP_W, sy = (float)mh / MAP_H;
|
||||
for (int ty = 0; ty < MAP_H; ty++) {
|
||||
for (int tx = 0; tx < MAP_W; tx++) {
|
||||
uint8_t fv = g->map.fog[ty*MAP_W + tx];
|
||||
if (fv == 0) continue;
|
||||
Tile *t = map_tile(&g->map, tx, ty);
|
||||
Uint8 cr=120,cg=104,cb=78;
|
||||
switch (t->terrain) {
|
||||
case T_WATER: cr=44;cg=78;cb=120; break;
|
||||
case T_ROCK: cr=110;cg=104;cb=98; break;
|
||||
case T_SCRAP: cr=200;cg=170;cb=70; break;
|
||||
case T_GRASS: cr=86;cg=120;cb=64; break;
|
||||
}
|
||||
if (fv == 1) { cr/=2; cg/=2; cb/=2; }
|
||||
SDL_SetRenderDrawColor(g->ren, cr, cg, cb, 255);
|
||||
SDL_RenderDrawPoint(g->ren, mx + (int)(tx*sx), my + (int)(ty*sy));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < g->unit_count; i++) {
|
||||
Unit *u = &g->units[i];
|
||||
if (u->dead) continue;
|
||||
if (u->faction != g->human) {
|
||||
int tx = (int)(u->x/TILE), ty = (int)(u->y/TILE);
|
||||
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*MAP_W+tx] != 2) continue;
|
||||
}
|
||||
SDL_Color c = faction_color(u->faction);
|
||||
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||
SDL_RenderDrawPoint(g->ren, mx + (int)(u->x/MAP_PIX_W*mw),
|
||||
my + (int)(u->y/MAP_PIX_H*mh));
|
||||
}
|
||||
for (int i = 0; i < g->bld_count; i++) {
|
||||
Building *b = &g->buildings[i];
|
||||
if (b->dead) continue;
|
||||
if (b->faction != g->human) {
|
||||
int tx = b->tx + b->w/2, ty = b->ty + b->h/2;
|
||||
if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*MAP_W+tx] != 2) continue;
|
||||
}
|
||||
SDL_Color c = faction_color(b->faction);
|
||||
int px = mx + (int)((b->tx*TILE)/MAP_PIX_W*mw);
|
||||
int py = my + (int)((b->ty*TILE)/MAP_PIX_H*mh);
|
||||
SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255);
|
||||
SDL_RenderFillRect(g->ren, &(SDL_Rect){px, py, 2, 2});
|
||||
}
|
||||
/* viewport box */
|
||||
int vx = mx + (int)(g->cam.x / MAP_PIX_W * mw);
|
||||
int vy = my + (int)(g->cam.y / MAP_PIX_H * mh);
|
||||
SDL_SetRenderDrawColor(g->ren, 255, 255, 255, 200);
|
||||
SDL_RenderDrawRect(g->ren, &(SDL_Rect){vx, vy,
|
||||
(int)(VIEW_W/MAP_PIX_W*mw), (int)(VIEW_H/MAP_PIX_H*mh)});
|
||||
}
|
||||
|
||||
static void render_wrapped(Game *g, TTF_Font *f, const char *txt,
|
||||
int x, int y, int maxw, SDL_Color c) {
|
||||
/* greedy word wrap */
|
||||
char buf[1024];
|
||||
strncpy(buf, txt, sizeof buf - 1); buf[sizeof buf - 1] = 0;
|
||||
char *tok = strtok(buf, " ");
|
||||
char line[256]; line[0] = 0;
|
||||
int ly = y;
|
||||
while (tok) {
|
||||
int cur = (int)strlen(line);
|
||||
int add = (int)strlen(tok) + (cur ? 1 : 0);
|
||||
if (cur + add >= (int)sizeof line || cur + add > maxw/8) {
|
||||
render_text(&g->spr, g->ren, f, line, x, ly, c);
|
||||
ly += 22; line[0] = 0; cur = 0;
|
||||
}
|
||||
if (cur) { strcat(line, " "); strcat(line, tok); }
|
||||
else strcpy(line, tok);
|
||||
tok = strtok(NULL, " ");
|
||||
}
|
||||
if (line[0]) render_text(&g->spr, g->ren, f, line, x, ly, c);
|
||||
}
|
||||
|
||||
void render_menu(Game *g) {
|
||||
SDL_SetRenderDrawColor(g->ren, 18, 16, 14, 255);
|
||||
SDL_RenderClear(g->ren);
|
||||
render_text(&g->spr, g->ren, g->spr.font_big,
|
||||
"KRUSH, KILL 'N' DESTROY", VIEW_W/2 - 260, 36, (SDL_Color){230,200,90,255});
|
||||
render_text(&g->spr, g->ren, g->spr.font_med,
|
||||
"Recreated post-apocalyptic RTS - choose a mission:", VIEW_W/2 - 230, 84,
|
||||
(SDL_Color){220,220,220,255});
|
||||
|
||||
int n = mission_count();
|
||||
int lx = 40, lw = 470, rh = 34;
|
||||
for (int i = 0; i < n; i++) {
|
||||
const Mission *m = mission_get(i);
|
||||
int y = 124 + i * rh;
|
||||
int locked = (m->id >= g->unlocked);
|
||||
int sel = (i == g->menu_sel);
|
||||
SDL_Rect r = { lx, y, lw, rh - 4 };
|
||||
SDL_SetRenderDrawColor(g->ren, sel ? 54 : 40, sel ? 60 : 44, sel ? 48 : 38, 255);
|
||||
SDL_RenderFillRect(g->ren, &r);
|
||||
SDL_SetRenderDrawColor(g->ren, sel ? 230 : 120, sel ? 200 : 200, sel ? 90 : 110, 255);
|
||||
SDL_RenderDrawRect(g->ren, &r);
|
||||
char line[200];
|
||||
const char *obj = (m->objective == OBJ_SURVIVE) ? "Survive"
|
||||
: (m->objective == OBJ_GATHER) ? "Gather"
|
||||
: "Destroy";
|
||||
snprintf(line, sizeof line, locked ? "%d. %s [LOCKED]"
|
||||
: "%d. %s (%s, %s)",
|
||||
m->id, m->name, obj, m->diff==DIFF_HARD ? "Hard" : "Normal");
|
||||
render_text(&g->spr, g->ren, g->spr.font_med, line, lx + 10, y + 4,
|
||||
locked ? (SDL_Color){120,120,120,255}
|
||||
: (SDL_Color){230,230,230,255});
|
||||
}
|
||||
|
||||
/* briefing panel for the highlighted mission */
|
||||
const Mission *m = mission_get(g->menu_sel);
|
||||
int px = lx + lw + 30, pw = VIEW_W - px - 30;
|
||||
render_text(&g->spr, g->ren, g->spr.font_big, m->name, px, 124,
|
||||
(SDL_Color){230,200,90,255});
|
||||
char meta[256];
|
||||
snprintf(meta, sizeof meta,
|
||||
"Enemy: %s Difficulty: %s Objective: %s",
|
||||
faction_name(m->enemy),
|
||||
m->diff==DIFF_HARD ? "Hard" : "Normal",
|
||||
m->objective==OBJ_SURVIVE ? "Survive the assault"
|
||||
: m->objective==OBJ_GATHER ? "Stockpile scrap" : "Destroy all enemies");
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, meta, px, 158,
|
||||
(SDL_Color){180,210,255,255});
|
||||
render_wrapped(g, g->spr.font_med, m->brief, px, 188, pw,
|
||||
(SDL_Color){220,220,220,255});
|
||||
|
||||
char hint[256];
|
||||
snprintf(hint, sizeof hint,
|
||||
"Up/Down select Enter start [F] side: %s 0-9 jump. Factions: Survivors/Evolved/Series 9.",
|
||||
faction_name(g->human));
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, hint,
|
||||
lx, VIEW_H - 28, (SDL_Color){150,150,150,255});
|
||||
SDL_RenderPresent(g->ren);
|
||||
}
|
||||
|
||||
void render_message_log(Game *g) {
|
||||
if (g->log_count <= 0) return;
|
||||
int x = 12;
|
||||
int y = VIEW_H - 200; /* above the build bar */
|
||||
int shown = (g->log_count < 6) ? g->log_count : 6;
|
||||
for (int i = 0; i < shown; i++) {
|
||||
int idx = (g->log_next - shown + i + 32) % 32;
|
||||
render_text(&g->spr, g->ren, g->spr.font_small, g->log_msg[idx],
|
||||
x, y + i * 18, (SDL_Color){210, 230, 180, 255});
|
||||
}
|
||||
}
|
||||
|
||||
void render_frame(Game *g) {
|
||||
if (g->state == GAME_MENU) { render_menu(g); return; }
|
||||
/* camera shake */
|
||||
float shx = 0, shy = 0;
|
||||
if (g->cam.shake > 0) {
|
||||
shx = ((float)(rand() % 21) - 10) / 10.0f * g->cam.shake;
|
||||
shy = ((float)(rand() % 21) - 10) / 10.0f * g->cam.shake;
|
||||
g->cam.x += shx; g->cam.y += shy;
|
||||
}
|
||||
SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 255);
|
||||
SDL_RenderClear(g->ren);
|
||||
render_world(g);
|
||||
render_wrecks(g);
|
||||
render_buildings(g);
|
||||
render_units(g);
|
||||
render_bullets(g);
|
||||
render_particles(g);
|
||||
render_selection(g);
|
||||
render_build_menu(g);
|
||||
render_hud(g);
|
||||
render_minimap(g);
|
||||
render_message_log(g);
|
||||
SDL_RenderPresent(g->ren);
|
||||
if (g->cam.shake > 0) { g->cam.x -= shx; g->cam.y -= shy; }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue