Forward HQ REFINERY + harvesters; denser clash scrap flecks; derrick/gantry/hopper silhouette in snapshot+sprites.
1184 lines
57 KiB
C
1184 lines
57 KiB
C
/*
|
||
* Software snapshot renderer - draws the game world into an RGBA buffer using
|
||
* only primitive shapes (no SDL gpu readback, which is broken on this box due
|
||
* to an SDL2/SDL3 conflict). Output is saved as PNG via png_save().
|
||
* Type-aware silhouettes mirror sprites.c so shot_*.png shows KKND2-like
|
||
* unit/building variety (not generic discs/rects).
|
||
*/
|
||
#include "kknd.h"
|
||
|
||
#define SW VIEW_W
|
||
#define SH VIEW_H
|
||
/* Internal render size (pre-upscale). Zoom 2 → ~640×360 world crop, then NN→1280×720.
|
||
* Wider crop keeps HQ + clash readable; bright fill + black outline does the rest. */
|
||
#define SHOT_ZOOM 2
|
||
/* Extra silhouette scale inside the shot crop (1.0 = sprites.c size). */
|
||
#define SHOT_UNIT_SCALE 1.45f
|
||
|
||
static uint8_t *g_buf;
|
||
static int g_sw = SW, g_sh = SH;
|
||
|
||
static void px(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||
if (x < 0 || y < 0 || x >= g_sw || y >= g_sh) return;
|
||
int i = (y * g_sw + x) * 4;
|
||
g_buf[i] = r; g_buf[i+1] = g; g_buf[i+2] = b; g_buf[i+3] = 255;
|
||
}
|
||
|
||
static void darken_px(int x, int y, float keep) {
|
||
if (x < 0 || y < 0 || x >= g_sw || y >= g_sh) return;
|
||
int i = (y * g_sw + x) * 4;
|
||
g_buf[i] = (uint8_t)(g_buf[i] * keep);
|
||
g_buf[i+1] = (uint8_t)(g_buf[i+1] * keep);
|
||
g_buf[i+2] = (uint8_t)(g_buf[i+2] * keep);
|
||
}
|
||
|
||
static void rectf(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
|
||
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }
|
||
if (y0 > y1) { int t = y0; y0 = y1; y1 = t; }
|
||
for (int y = y0; y <= y1; y++)
|
||
for (int x = x0; x <= x1; x++)
|
||
px(x, y, r, g, b);
|
||
}
|
||
|
||
static void fill_rect(int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
|
||
if (w <= 0 || h <= 0) return;
|
||
rectf(x, y, x + w - 1, y + h - 1, r, g, b);
|
||
}
|
||
|
||
static void disc(int cx, int cy, int rad, uint8_t r, uint8_t g, uint8_t b) {
|
||
for (int y = -rad; y <= rad; y++)
|
||
for (int x = -rad; x <= rad; x++)
|
||
if (x*x + y*y <= rad*rad) px(cx+x, cy+y, r, g, b);
|
||
}
|
||
|
||
static void ring(int cx, int cy, int rad, int t, uint8_t r, uint8_t g, uint8_t b) {
|
||
int inner = rad - t;
|
||
if (inner < 0) inner = 0;
|
||
for (int y = -rad; y <= rad; y++)
|
||
for (int x = -rad; x <= rad; x++) {
|
||
int d = x*x + y*y;
|
||
if (d <= rad*rad && d >= inner*inner) px(cx+x, cy+y, r, g, b);
|
||
}
|
||
}
|
||
|
||
static void line(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
|
||
int dx = abs(x1 - x0), dy = -abs(y1 - y0);
|
||
int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
|
||
int err = dx + dy;
|
||
for (;;) {
|
||
px(x0, y0, r, g, b);
|
||
if (x0 == x1 && y0 == y1) break;
|
||
int e2 = 2 * err;
|
||
if (e2 >= dy) { err += dy; x0 += sx; }
|
||
if (e2 <= dx) { err += dx; y0 += sy; }
|
||
}
|
||
}
|
||
|
||
/* Saturated faction paints — lime Survivors / hot magenta Evolved on bright sand. */
|
||
static uint8_t faction_r(Faction f){ return f==FACTION_SURVIVOR?55 : f==FACTION_EVOLVED?255 : 240; }
|
||
static uint8_t faction_g(Faction f){ return f==FACTION_SURVIVOR?235: f==FACTION_EVOLVED?40 : 55; }
|
||
static uint8_t faction_b(Faction f){ return f==FACTION_SURVIVOR?55 : f==FACTION_EVOLVED?255 : 240; }
|
||
|
||
static void darken3(uint8_t r, uint8_t g, uint8_t b, float f,
|
||
uint8_t *or, uint8_t *og, uint8_t *ob) {
|
||
*or = (uint8_t)(r * f); *og = (uint8_t)(g * f); *ob = (uint8_t)(b * f);
|
||
}
|
||
static void lighten3(uint8_t r, uint8_t g, uint8_t b, float f,
|
||
uint8_t *or, uint8_t *og, uint8_t *ob) {
|
||
*or = (uint8_t)(r + (255 - r) * f);
|
||
*og = (uint8_t)(g + (255 - g) * f);
|
||
*ob = (uint8_t)(b + (255 - b) * f);
|
||
}
|
||
|
||
/* Classic KKND2 wasteland: yellow-tan sand (R>>B, G slightly under R). */
|
||
static void terrain_color(TerrainType t, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||
switch (t) {
|
||
case T_GRASS: *r=242; *g=198; *b=92; break; /* sunlit desert */
|
||
case T_ROAD: *r=175; *g=148; *b=98; break;
|
||
case T_RUBBLE:*r=168; *g=138; *b=88; break;
|
||
case T_ROCK: *r=155; *g=142; *b=128; break;
|
||
case T_WATER: *r=72; *g=128; *b=178; break;
|
||
case T_SCRAP: *r=240; *g=190; *b=78; break;
|
||
default: *r=228; *g=188; *b=98; break;
|
||
}
|
||
}
|
||
|
||
/* Spoke (wheeled) or hash (tracked) marks on a tyre disc; phase = Unit.anim rad. */
|
||
static void tyre_mark(int cx, int cy, int rad, float phase, int wheeled) {
|
||
uint8_t sr = 205, sg = 200, sb = 180;
|
||
int n = wheeled ? 4 : 3;
|
||
float step = (float)(2.0 * M_PI / (double)n);
|
||
float r0 = wheeled ? rad * 0.30f : rad * 0.55f;
|
||
float r1 = rad * 0.90f;
|
||
for (int i = 0; i < n; i++) {
|
||
float a = phase + i * step;
|
||
int x1 = cx + (int)(cosf(a) * r0);
|
||
int y1 = cy + (int)(sinf(a) * r0);
|
||
int x2 = cx + (int)(cosf(a) * r1);
|
||
int y2 = cy + (int)(sinf(a) * r1);
|
||
line(x1, y1, x2, y2, sr, sg, sb);
|
||
}
|
||
}
|
||
|
||
/* Classic RTS silhouette: thin black outline, then bright faction fill.
|
||
* Dark outer discs previously read as olive-mud blobs under NN zoom. */
|
||
static void draw_unit(Unit *u, int sx, int sy) {
|
||
uint8_t br = faction_r(u->faction), bg = faction_g(u->faction), bb = faction_b(u->faction);
|
||
uint8_t lr, lg, lb;
|
||
lighten3(br, bg, bb, 0.45f, &lr, &lg, &lb);
|
||
uint8_t ok = 18, og = 16, ob = 14; /* near-black outline */
|
||
uint8_t mr = 70, mg = 65, mb = 58; /* tyre */
|
||
float c = cosf(u->heading), s = sinf(u->heading);
|
||
const float sc = SHOT_UNIT_SCALE;
|
||
float ph = u->anim; /* continuous roll/walk phase for wheeled/tracked/footed */
|
||
#define SI(n) ((int)((n) * sc + 0.5f))
|
||
#define LX(lx, ly) (sx + (int)((((lx)-15)*sc)*c - (((ly)-15)*sc)*s))
|
||
#define LY(lx, ly) (sy + (int)((((lx)-15)*sc)*s + (((ly)-15)*sc)*c))
|
||
|
||
/* Ground shadow — depth cue so bodies don't melt into sand. */
|
||
disc(sx + SI(2), sy + SI(3), SI(9), 55, 48, 28);
|
||
|
||
/* Foot infantry/mutant: vertical body bob; shadow stays grounded. */
|
||
if (u->type == UT_INFANTRY || u->type == UT_MUTANT) {
|
||
float bob = sinf(ph) * 2.5f * sc;
|
||
sy += (int)(bob + (bob >= 0.0f ? 0.5f : -0.5f));
|
||
}
|
||
|
||
switch (u->type) {
|
||
case UT_INFANTRY:
|
||
/* legs → torso → helmet/visor → pack → thick rifle (KKND2 foot soldier) */
|
||
disc(LX(11,23), LY(11,23), SI(3), ok, og, ob);
|
||
disc(LX(19,23), LY(19,23), SI(3), ok, og, ob);
|
||
disc(LX(11,23), LY(11,23), SI(2), br, bg, bb);
|
||
disc(LX(19,23), LY(19,23), SI(2), br, bg, bb);
|
||
disc(sx, sy, SI(9), ok, og, ob);
|
||
disc(sx, sy, SI(7), br, bg, bb);
|
||
disc(LX(10,16), LY(10,16), SI(3), ok, og, ob); /* backpack */
|
||
disc(LX(10,16), LY(10,16), SI(2), 50, 46, 40);
|
||
disc(LX(15,8), LY(15,8), SI(5), ok, og, ob);
|
||
disc(LX(15,8), LY(15,8), SI(4), lr, lg, lb); /* helmet */
|
||
fill_rect(LX(13,8)-SI(1), LY(13,8)-1, SI(5), SI(2), 30, 40, 50); /* visor */
|
||
line(LX(16,14), LY(16,14), LX(16,0), LY(16,0), 40, 36, 30);
|
||
line(LX(17,14), LY(17,14), LX(17,1), LY(17,1), 55, 50, 40);
|
||
line(LX(18,14), LY(18,14), LX(18,2), LY(18,2), 45, 40, 34);
|
||
disc(LX(17,1), LY(17,1), SI(2), 60, 55, 45); /* muzzle tip */
|
||
break;
|
||
case UT_BUGGY:
|
||
/* cabin + windshield + chassis + hubbed wheels + thick MG */
|
||
fill_rect(sx-SI(12), sy-SI(8), SI(24), SI(16), ok, og, ob);
|
||
fill_rect(sx-SI(10), sy-SI(6), SI(20), SI(12), br, bg, bb);
|
||
fill_rect(LX(7,10)-SI(1), LY(7,10)-SI(1), SI(10), SI(6), lr, lg, lb); /* cabin */
|
||
fill_rect(LX(8,9)-SI(1), LY(8,9)-SI(1), SI(8), SI(3), 35, 55, 75); /* windshield */
|
||
disc(LX(8,7), LY(8,7), SI(5), ok, og, ob);
|
||
disc(LX(22,7), LY(22,7), SI(5), ok, og, ob);
|
||
disc(LX(8,23), LY(8,23), SI(5), ok, og, ob);
|
||
disc(LX(22,23), LY(22,23), SI(5), ok, og, ob);
|
||
disc(LX(8,7), LY(8,7), SI(4), mr, mg, mb);
|
||
disc(LX(22,7), LY(22,7), SI(4), mr, mg, mb);
|
||
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
|
||
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
|
||
disc(LX(8,7), LY(8,7), SI(2), 90, 85, 75); /* hub */
|
||
disc(LX(22,7), LY(22,7), SI(2), 90, 85, 75);
|
||
disc(LX(8,23), LY(8,23), SI(2), 90, 85, 75);
|
||
disc(LX(22,23), LY(22,23), SI(2), 90, 85, 75);
|
||
tyre_mark(LX(8,7), LY(8,7), SI(4), ph, 1);
|
||
tyre_mark(LX(22,7), LY(22,7), SI(4), ph, 1);
|
||
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 1);
|
||
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 1);
|
||
line(sx, sy, LX(29,14), LY(29,14), 40, 36, 30);
|
||
line(sx, sy, LX(29,15), LY(29,15), 50, 45, 35);
|
||
line(sx, sy, LX(29,16), LY(29,16), 40, 36, 30);
|
||
disc(LX(29,15), LY(29,15), SI(2), 60, 55, 45);
|
||
break;
|
||
case UT_TANK:
|
||
/* side tracks + sprockets + hull + cupola + thick barrel */
|
||
fill_rect(LX(3,10)-SI(1), LY(3,10)-SI(2), SI(5), SI(12), ok, og, ob);
|
||
fill_rect(LX(24,10)-SI(1), LY(24,10)-SI(2), SI(5), SI(12), ok, og, ob);
|
||
fill_rect(LX(4,11)-SI(1), LY(4,11)-SI(1), SI(3), SI(10), mr, mg, mb);
|
||
fill_rect(LX(25,11)-SI(1), LY(25,11)-SI(1), SI(3), SI(10), mr, mg, mb);
|
||
disc(LX(5,11), LY(5,11), SI(2), 40, 38, 32); /* sprocket */
|
||
disc(LX(5,20), LY(5,20), SI(2), 40, 38, 32);
|
||
disc(LX(26,11), LY(26,11), SI(2), 40, 38, 32);
|
||
disc(LX(26,20), LY(26,20), SI(2), 40, 38, 32);
|
||
fill_rect(sx-SI(11), sy-SI(5), SI(22), SI(10), ok, og, ob);
|
||
fill_rect(sx-SI(9), sy-SI(4), SI(18), SI(8), br, bg, bb);
|
||
disc(sx, sy, SI(7), ok, og, ob);
|
||
disc(sx, sy, SI(6), lr, lg, lb); /* cupola */
|
||
disc(LX(15,12), LY(15,12), SI(2), 40, 38, 32); /* hatch */
|
||
line(LX(15,13), LY(15,13), LX(29,13), LY(29,13), 45, 40, 32);
|
||
line(LX(15,14), LY(15,14), LX(29,14), LY(29,14), 50, 45, 35);
|
||
line(LX(15,15), LY(15,15), LX(29,15), LY(29,15), 55, 50, 40);
|
||
line(LX(15,16), LY(15,16), LX(29,16), LY(29,16), 50, 45, 35);
|
||
line(LX(15,17), LY(15,17), LX(28,17), LY(28,17), 45, 40, 32);
|
||
disc(LX(29,15), LY(29,15), SI(2), 60, 55, 45); /* muzzle */
|
||
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
|
||
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
|
||
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 0);
|
||
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 0);
|
||
break;
|
||
case UT_ARTILLERY:
|
||
/* chassis + long thick barrel tube + muzzle brake */
|
||
fill_rect(sx-SI(13), sy-SI(5), SI(26), SI(12), ok, og, ob);
|
||
fill_rect(sx-SI(11), sy-SI(3), SI(22), SI(8), br, bg, bb);
|
||
disc(sx, sy, SI(6), ok, og, ob);
|
||
disc(sx, sy, SI(5), lr, lg, lb);
|
||
line(LX(15,13), LY(15,13), LX(4,2), LY(4,2), 45, 40, 32);
|
||
line(LX(15,14), LY(15,14), LX(4,3), LY(4,3), 50, 45, 35);
|
||
line(LX(15,15), LY(15,15), LX(3,2), LY(3,2), 55, 50, 40);
|
||
line(LX(15,16), LY(15,16), LX(4,4), LY(4,4), 50, 45, 35);
|
||
line(LX(15,17), LY(15,17), LX(5,5), LY(5,5), 45, 40, 32);
|
||
disc(LX(4,3), LY(4,3), SI(3), 60, 55, 45); /* muzzle */
|
||
disc(LX(4,3), LY(4,3), SI(1), 30, 28, 24);
|
||
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
|
||
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
|
||
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 0);
|
||
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 0);
|
||
break;
|
||
case UT_HARVESTER: {
|
||
/* cabin + scoop lip + cargo bed (gold by cargo) + hubbed dual wheels */
|
||
fill_rect(sx-SI(14), sy-SI(9), SI(28), SI(18), ok, og, ob);
|
||
fill_rect(sx-SI(12), sy-SI(7), SI(24), SI(14), br, bg, bb);
|
||
fill_rect(LX(4,10)-SI(1), LY(4,10)-SI(2), SI(8), SI(8), lr, lg, lb); /* cabin */
|
||
fill_rect(LX(5,9)-SI(1), LY(5,9)-SI(1), SI(6), SI(3), 35, 55, 75); /* glass */
|
||
fill_rect(LX(2,14)-SI(1), LY(2,14)-SI(1), SI(4), SI(5), 90, 80, 55); /* scoop */
|
||
int bed_w = SI(14), bed_h = SI(10);
|
||
int bed_x = sx - SI(4), bed_y = sy - SI(5);
|
||
fill_rect(bed_x, bed_y, bed_w, bed_h, 55, 48, 32);
|
||
float cap = (float)unit_def(u->type, u->faction)->cargo;
|
||
float fill = (cap > 0.0f) ? (u->cargo / cap) : 0.0f;
|
||
if (fill < 0.0f) fill = 0.0f;
|
||
if (fill > 1.0f) fill = 1.0f;
|
||
int fw = (int)(bed_w * fill + 0.5f);
|
||
if (fw > 0)
|
||
fill_rect(bed_x, bed_y, fw, bed_h, 200, 170, 90);
|
||
if (fill > 0.15f) {
|
||
line(LX(14,10), LY(14,10), LX(26,10), LY(26,10), 160, 130, 70);
|
||
line(LX(14,13), LY(14,13), LX(26,13), LY(26,13), 160, 130, 70);
|
||
line(LX(14,16), LY(14,16), LX(26,16), LY(26,16), 160, 130, 70);
|
||
}
|
||
disc(LX(8,23), LY(8,23), SI(5), ok, og, ob);
|
||
disc(LX(22,23), LY(22,23), SI(5), ok, og, ob);
|
||
disc(LX(8,23), LY(8,23), SI(4), mr, mg, mb);
|
||
disc(LX(22,23), LY(22,23), SI(4), mr, mg, mb);
|
||
disc(LX(8,23), LY(8,23), SI(2), 90, 85, 75);
|
||
disc(LX(22,23), LY(22,23), SI(2), 90, 85, 75);
|
||
tyre_mark(LX(8,23), LY(8,23), SI(4), ph, 1);
|
||
tyre_mark(LX(22,23), LY(22,23), SI(4), ph, 1);
|
||
line(LX(5,7), LY(5,7), LX(5,2), LY(5,2), lr, lg, lb);
|
||
line(LX(6,7), LY(6,7), LX(6,3), LY(6,3), 50, 46, 40);
|
||
break;
|
||
}
|
||
case UT_JEEP:
|
||
/* open cabin + thick roll cage + hubbed wheels + twin MG */
|
||
fill_rect(sx-SI(11), sy-SI(6), SI(22), SI(12), ok, og, ob);
|
||
fill_rect(sx-SI(9), sy-SI(4), SI(18), SI(8), br, bg, bb);
|
||
fill_rect(LX(8,11)-SI(1), LY(8,11)-SI(1), SI(8), SI(5), lr, lg, lb);
|
||
disc(LX(9,22), LY(9,22), SI(5), ok, og, ob);
|
||
disc(LX(21,22), LY(21,22), SI(5), ok, og, ob);
|
||
disc(LX(9,22), LY(9,22), SI(4), mr, mg, mb);
|
||
disc(LX(21,22), LY(21,22), SI(4), mr, mg, mb);
|
||
disc(LX(9,22), LY(9,22), SI(2), 90, 85, 75);
|
||
disc(LX(21,22), LY(21,22), SI(2), 90, 85, 75);
|
||
tyre_mark(LX(9,22), LY(9,22), SI(4), ph, 1);
|
||
tyre_mark(LX(21,22), LY(21,22), SI(4), ph, 1);
|
||
line(LX(11,11), LY(11,11), LX(11,3), LY(11,3), lr, lg, lb);
|
||
line(LX(12,11), LY(12,11), LX(12,3), LY(12,3), 50, 46, 40);
|
||
line(LX(19,11), LY(19,11), LX(19,3), LY(19,3), lr, lg, lb);
|
||
line(LX(18,11), LY(18,11), LX(18,3), LY(18,3), 50, 46, 40);
|
||
line(LX(11,3), LY(11,3), LX(19,3), LY(19,3), lr, lg, lb);
|
||
line(LX(11,4), LY(11,4), LX(19,4), LY(19,4), 50, 46, 40);
|
||
line(sx, sy, LX(28,14), LY(28,14), 40, 36, 30);
|
||
line(sx, sy, LX(28,15), LY(28,15), 50, 45, 35);
|
||
line(sx, sy, LX(28,16), LY(28,16), 40, 36, 30);
|
||
disc(LX(28,15), LY(28,15), SI(2), 60, 55, 45);
|
||
break;
|
||
case UT_MUTANT:
|
||
/* hunched bulk + brow ridge + thick spikes + claw hand */
|
||
disc(sx, sy+SI(2), SI(11), ok, og, ob);
|
||
disc(sx, sy, SI(9), br, bg, bb);
|
||
disc(LX(15,9), LY(15,9), SI(5), ok, og, ob);
|
||
disc(LX(15,9), LY(15,9), SI(4), lr, lg, lb);
|
||
fill_rect(LX(12,8)-SI(1), LY(12,8)-1, SI(7), SI(2), 30, 40, 50); /* brow */
|
||
line(LX(12,6), LY(12,6), LX(9,0), LY(9,0), 40, 36, 30);
|
||
line(LX(13,6), LY(13,6), LX(10,1), LY(10,1), 55, 50, 40);
|
||
line(LX(15,5), LY(15,5), LX(15,0), LY(15,0), 40, 36, 30);
|
||
line(LX(16,5), LY(16,5), LX(16,0), LY(16,0), 55, 50, 40);
|
||
line(LX(18,6), LY(18,6), LX(21,0), LY(21,0), 40, 36, 30);
|
||
line(LX(17,6), LY(17,6), LX(20,1), LY(20,1), 55, 50, 40);
|
||
line(sx, sy+SI(1), LX(26,8), LY(26,8), 40, 36, 30);
|
||
line(sx, sy+SI(2), LX(26,9), LY(26,9), 50, 45, 35);
|
||
line(LX(24,9), LY(24,9), LX(29,5), LY(29,5), 55, 50, 40);
|
||
line(LX(24,9), LY(24,9), LX(29,11), LY(29,11), 55, 50, 40);
|
||
line(LX(24,10), LY(24,10), LX(28,8), LY(28,8), 70, 60, 45);
|
||
disc(LX(28,8), LY(28,8), SI(2), 70, 60, 45); /* claw tip */
|
||
disc(LX(10,22), LY(10,22), SI(4), ok, og, ob);
|
||
disc(LX(20,22), LY(20,22), SI(4), ok, og, ob);
|
||
disc(LX(10,22), LY(10,22), SI(3), br, bg, bb);
|
||
disc(LX(20,22), LY(20,22), SI(3), br, bg, bb);
|
||
break;
|
||
case UT_BEAST:
|
||
/* armored carapace + thick snout/tusks + hubbed multi-wheel */
|
||
fill_rect(sx-SI(10), sy-SI(6), SI(20), SI(12), ok, og, ob);
|
||
fill_rect(sx-SI(8), sy-SI(4), SI(16), SI(8), br, bg, bb);
|
||
fill_rect(sx-SI(6), sy-SI(5), SI(12), SI(3), lr, lg, lb); /* carapace ridge */
|
||
disc(LX(9,21), LY(9,21), SI(4), ok, og, ob);
|
||
disc(LX(14,22), LY(14,22), SI(4), ok, og, ob);
|
||
disc(LX(19,21), LY(19,21), SI(4), ok, og, ob);
|
||
disc(LX(23,22), LY(23,22), SI(4), ok, og, ob);
|
||
disc(LX(9,21), LY(9,21), SI(3), mr, mg, mb);
|
||
disc(LX(14,22), LY(14,22), SI(3), mr, mg, mb);
|
||
disc(LX(19,21), LY(19,21), SI(3), mr, mg, mb);
|
||
disc(LX(23,22), LY(23,22), SI(3), mr, mg, mb);
|
||
tyre_mark(LX(9,21), LY(9,21), SI(3), ph, 1);
|
||
tyre_mark(LX(14,22), LY(14,22), SI(3), ph, 1);
|
||
tyre_mark(LX(19,21), LY(19,21), SI(3), ph, 1);
|
||
tyre_mark(LX(23,22), LY(23,22), SI(3), ph, 1);
|
||
disc(LX(24,12), LY(24,12), SI(6), ok, og, ob);
|
||
disc(LX(24,12), LY(24,12), SI(5), lr, lg, lb); /* snout */
|
||
line(LX(26,10), LY(26,10), LX(29,8), LY(29,8), 40, 36, 30); /* tusk */
|
||
line(LX(26,14), LY(26,14), LX(29,16), LY(29,16), 40, 36, 30);
|
||
line(LX(26,12), LY(26,12), LX(29,12), LY(29,12), 55, 50, 40);
|
||
disc(LX(29,12), LY(29,12), SI(2), 60, 55, 45);
|
||
break;
|
||
case UT_RAVAGER:
|
||
/* heavy skirts + sprockets + cupola hatch + thick multi-barrel */
|
||
fill_rect(LX(1,9)-SI(1), LY(1,9)-SI(2), SI(5), SI(14), ok, og, ob);
|
||
fill_rect(LX(26,9)-SI(1), LY(26,9)-SI(2), SI(5), SI(14), ok, og, ob);
|
||
fill_rect(LX(2,11)-SI(1), LY(2,11)-SI(1), SI(3), SI(10), mr, mg, mb);
|
||
fill_rect(LX(27,11)-SI(1), LY(27,11)-SI(1), SI(3), SI(10), mr, mg, mb);
|
||
disc(LX(3,12), LY(3,12), SI(2), 40, 38, 32);
|
||
disc(LX(3,20), LY(3,20), SI(2), 40, 38, 32);
|
||
disc(LX(28,12), LY(28,12), SI(2), 40, 38, 32);
|
||
disc(LX(28,20), LY(28,20), SI(2), 40, 38, 32);
|
||
fill_rect(sx-SI(13), sy-SI(6), SI(26), SI(12), ok, og, ob);
|
||
fill_rect(sx-SI(11), sy-SI(5), SI(22), SI(10), br, bg, bb);
|
||
disc(sx, sy, SI(8), ok, og, ob);
|
||
disc(sx, sy, SI(7), lr, lg, lb);
|
||
disc(LX(15,12), LY(15,12), SI(2), 40, 38, 32); /* hatch */
|
||
line(LX(15,13), LY(15,13), LX(29,13), LY(29,13), 45, 40, 32);
|
||
line(LX(15,14), LY(15,14), LX(29,14), LY(29,14), 50, 45, 35);
|
||
line(LX(15,15), LY(15,15), LX(29,15), LY(29,15), 55, 50, 40);
|
||
line(LX(15,16), LY(15,16), LX(29,16), LY(29,16), 50, 45, 35);
|
||
line(LX(15,17), LY(15,17), LX(28,17), LY(28,17), 45, 40, 32);
|
||
disc(LX(29,15), LY(29,15), SI(2), 60, 55, 45);
|
||
disc(LX(7,23), LY(7,23), SI(5), ok, og, ob);
|
||
disc(LX(23,23), LY(23,23), SI(5), ok, og, ob);
|
||
disc(LX(7,23), LY(7,23), SI(4), mr, mg, mb);
|
||
disc(LX(23,23), LY(23,23), SI(4), mr, mg, mb);
|
||
tyre_mark(LX(7,23), LY(7,23), SI(4), ph, 0);
|
||
tyre_mark(LX(23,23), LY(23,23), SI(4), ph, 0);
|
||
break;
|
||
case UT_FLAME:
|
||
/* fuel pack valves + thick nozzle jet + helmet */
|
||
fill_rect(sx-SI(8), sy-SI(3), SI(16), SI(12), ok, og, ob);
|
||
fill_rect(sx-SI(6), sy-SI(1), SI(12), SI(9), br, bg, bb);
|
||
disc(LX(15,9), LY(15,9), SI(5), ok, og, ob);
|
||
disc(LX(15,9), LY(15,9), SI(4), lr, lg, lb);
|
||
fill_rect(LX(13,9)-SI(1), LY(13,9)-1, SI(5), SI(2), 30, 40, 50); /* visor */
|
||
disc(LX(8,14), LY(8,14), SI(6), ok, og, ob); /* fuel tank */
|
||
disc(LX(8,14), LY(8,14), SI(5), 90, 70, 40);
|
||
disc(LX(8,14), LY(8,14), SI(3), 200, 120, 40);
|
||
disc(LX(8,12), LY(8,12), SI(1), 60, 55, 45); /* valve */
|
||
disc(LX(8,16), LY(8,16), SI(1), 60, 55, 45);
|
||
line(LX(18,14), LY(18,14), LX(28,19), LY(28,19), 255, 120, 40);
|
||
line(LX(18,15), LY(18,15), LX(28,20), LY(28,20), 255, 150, 50);
|
||
line(LX(18,16), LY(18,16), LX(27,21), LY(27,21), 255, 180, 60);
|
||
line(LX(18,17), LY(18,17), LX(26,21), LY(26,21), 255, 200, 80);
|
||
disc(LX(28,20), LY(28,20), SI(3), 255, 200, 80);
|
||
disc(LX(28,20), LY(28,20), SI(1), 255, 240, 160);
|
||
break;
|
||
case UT_MISSILE:
|
||
/* dual tube pods + rail tips + hubbed chassis */
|
||
fill_rect(sx-SI(11), sy-SI(4), SI(22), SI(14), ok, og, ob);
|
||
fill_rect(sx-SI(9), sy-SI(2), SI(18), SI(10), br, bg, bb);
|
||
fill_rect(LX(12,7)-SI(1), LY(12,7)-SI(2), SI(10), SI(5), ok, og, ob);
|
||
fill_rect(LX(13,6)-SI(1), LY(13,6)-SI(1), SI(8), SI(3), 255, 80, 60);
|
||
fill_rect(LX(12,13)-SI(1), LY(12,13)-SI(2), SI(10), SI(5), ok, og, ob);
|
||
fill_rect(LX(13,12)-SI(1), LY(13,12)-SI(1), SI(8), SI(3), 255, 100, 70);
|
||
disc(LX(21,7), LY(21,7), SI(2), 255, 120, 80); /* nose cones */
|
||
disc(LX(21,13), LY(21,13), SI(2), 255, 140, 90);
|
||
disc(LX(9,23), LY(9,23), SI(5), ok, og, ob);
|
||
disc(LX(21,23), LY(21,23), SI(5), ok, og, ob);
|
||
disc(LX(9,23), LY(9,23), SI(4), mr, mg, mb);
|
||
disc(LX(21,23), LY(21,23), SI(4), mr, mg, mb);
|
||
disc(LX(9,23), LY(9,23), SI(2), 90, 85, 75);
|
||
disc(LX(21,23), LY(21,23), SI(2), 90, 85, 75);
|
||
tyre_mark(LX(9,23), LY(9,23), SI(4), ph, 1);
|
||
tyre_mark(LX(21,23), LY(21,23), SI(4), ph, 1);
|
||
line(sx, sy, LX(28,5), LY(28,5), 255, 80, 60);
|
||
line(sx, sy, LX(28,6), LY(28,6), 255, 100, 70);
|
||
disc(LX(28,6), LY(28,6), SI(2), 255, 140, 90);
|
||
break;
|
||
case UT_MEDIC:
|
||
/* white van + thick red cross + hubbed wheels + light bar */
|
||
fill_rect(sx-SI(10), sy-SI(5), SI(20), SI(15), ok, og, ob);
|
||
fill_rect(sx-SI(8), sy-SI(3), SI(16), SI(11), 245, 245, 245);
|
||
fill_rect(LX(7,10)-SI(1), LY(7,10)-SI(1), SI(6), SI(4), 200, 220, 240);
|
||
fill_rect(LX(12,6)-SI(2), LY(12,6)-1, SI(8), SI(2), 240, 50, 50); /* light bar */
|
||
disc(LX(10,24), LY(10,24), SI(5), ok, og, ob);
|
||
disc(LX(20,24), LY(20,24), SI(5), ok, og, ob);
|
||
disc(LX(10,24), LY(10,24), SI(4), mr, mg, mb);
|
||
disc(LX(20,24), LY(20,24), SI(4), mr, mg, mb);
|
||
disc(LX(10,24), LY(10,24), SI(2), 90, 85, 75);
|
||
disc(LX(20,24), LY(20,24), SI(2), 90, 85, 75);
|
||
tyre_mark(LX(10,24), LY(10,24), SI(4), ph, 1);
|
||
tyre_mark(LX(20,24), LY(20,24), SI(4), ph, 1);
|
||
disc(LX(15,9), LY(15,9), SI(6), 240, 50, 50);
|
||
fill_rect(LX(14,5)-1, LY(14,5)-SI(4), SI(3), SI(9), 255, 255, 255);
|
||
fill_rect(LX(11,8)-SI(4), LY(11,8)-1, SI(9), SI(3), 255, 255, 255);
|
||
break;
|
||
default:
|
||
disc(sx, sy, SI(10), ok, og, ob);
|
||
disc(sx, sy, SI(8), br, bg, bb);
|
||
break;
|
||
}
|
||
/* heading tick */
|
||
{
|
||
int hx = sx + (int)(c * 14 * sc), hy = sy + (int)(s * 14 * sc);
|
||
line(sx, sy, hx, hy, 250, 245, 210);
|
||
}
|
||
/* HP bar above unit (classic RTS strip) */
|
||
if (u->max_hp > 0) {
|
||
int bw = SI(28), by = sy - SI(18);
|
||
float f = u->hp / u->max_hp;
|
||
if (f < 0) f = 0; if (f > 1) f = 1;
|
||
fill_rect(sx - bw/2 - 1, by - 1, bw + 2, 6, 20, 18, 14);
|
||
fill_rect(sx - bw/2, by, bw, 4, 55, 48, 36);
|
||
uint8_t hr = (uint8_t)(255 * (1.0f - f)), hg = (uint8_t)(230 * f);
|
||
fill_rect(sx - bw/2, by, (int)(bw * f + 0.5f), 4, hr, hg, 40);
|
||
}
|
||
if (u->selected) {
|
||
int rad = SI((u->type == UT_TANK || u->type == UT_BEAST || u->type == UT_RAVAGER) ? 16 : 13);
|
||
ring(sx, sy, rad, 2, 255, 245, 60);
|
||
}
|
||
#undef LX
|
||
#undef LY
|
||
#undef SI
|
||
}
|
||
|
||
/* Charred wreck silhouettes — dim faction paint + scorch, type-aware hull. */
|
||
static void draw_wreck(Wreck *w, int sx, int sy) {
|
||
uint8_t br = faction_r(w->faction), bg = faction_g(w->faction), bb = faction_b(w->faction);
|
||
uint8_t dr, dg, db;
|
||
darken3(br, bg, bb, 0.38f, &dr, &dg, &db);
|
||
uint8_t ok = 28, og = 24, ob = 20;
|
||
uint8_t ch = 48, cg = 42, cb = 36; /* char */
|
||
float c = cosf(w->rot), s = sinf(w->rot);
|
||
const float sc = SHOT_UNIT_SCALE * 0.92f;
|
||
#define SI(n) ((int)((n) * sc + 0.5f))
|
||
#define LX(lx, ly) (sx + (int)((((lx)-15)*sc)*c - (((ly)-15)*sc)*s))
|
||
#define LY(lx, ly) (sy + (int)((((lx)-15)*sc)*s + (((ly)-15)*sc)*c))
|
||
|
||
disc(sx + SI(2), sy + SI(4), SI(10), 42, 36, 28); /* scorch stain */
|
||
|
||
switch (w->type) {
|
||
case UT_HARVESTER:
|
||
fill_rect(sx - SI(14), sy - SI(9), SI(28), SI(18), ok, og, ob);
|
||
fill_rect(sx - SI(12), sy - SI(7), SI(24), SI(14), dr, dg, db);
|
||
fill_rect(LX(6, 12) - SI(1), LY(6, 12) - SI(1), SI(10), SI(8), ch, cg, cb);
|
||
disc(LX(8, 8), LY(8, 8), SI(4), ok, og, ob);
|
||
disc(LX(22, 8), LY(22, 8), SI(4), ok, og, ob);
|
||
disc(LX(8, 22), LY(8, 22), SI(4), ok, og, ob);
|
||
disc(LX(22, 22), LY(22, 22), SI(4), ok, og, ob);
|
||
fill_rect(sx - SI(4), sy - SI(2), SI(10), SI(5), 55, 48, 40); /* torn bed */
|
||
break;
|
||
case UT_TANK:
|
||
case UT_RAVAGER:
|
||
case UT_BEAST:
|
||
fill_rect(sx - SI(13), sy - SI(8), SI(26), SI(16), ok, og, ob);
|
||
fill_rect(sx - SI(11), sy - SI(6), SI(22), SI(12), dr, dg, db);
|
||
fill_rect(sx - SI(12), sy - SI(3), SI(4), SI(8), ch, cg, cb); /* track */
|
||
fill_rect(sx + SI(8), sy - SI(3), SI(4), SI(8), ch, cg, cb);
|
||
disc(sx, sy - SI(2), SI(6), ok, og, ob);
|
||
disc(sx, sy - SI(2), SI(4), ch, cg, cb); /* dead turret */
|
||
line(LX(15, 8), LY(15, 8), LX(15, -2), LY(15, -2), 40, 36, 30); /* bent barrel */
|
||
break;
|
||
case UT_ARTILLERY:
|
||
case UT_MISSILE:
|
||
fill_rect(sx - SI(12), sy - SI(7), SI(24), SI(14), ok, og, ob);
|
||
fill_rect(sx - SI(10), sy - SI(5), SI(20), SI(10), dr, dg, db);
|
||
disc(LX(8, 20), LY(8, 20), SI(4), ok, og, ob);
|
||
disc(LX(22, 20), LY(22, 20), SI(4), ok, og, ob);
|
||
line(LX(12, 12), LY(12, 12), LX(22, 2), LY(22, 2), ch, cg, cb);
|
||
break;
|
||
case UT_BUGGY:
|
||
case UT_JEEP:
|
||
case UT_FLAME:
|
||
case UT_MEDIC:
|
||
fill_rect(sx - SI(11), sy - SI(7), SI(22), SI(14), ok, og, ob);
|
||
fill_rect(sx - SI(9), sy - SI(5), SI(18), SI(10), dr, dg, db);
|
||
disc(LX(8, 8), LY(8, 8), SI(4), ok, og, ob);
|
||
disc(LX(22, 8), LY(22, 8), SI(4), ok, og, ob);
|
||
disc(LX(8, 22), LY(8, 22), SI(4), ok, og, ob);
|
||
disc(LX(22, 22), LY(22, 22), SI(4), ok, og, ob);
|
||
fill_rect(LX(10, 10) - SI(1), LY(10, 10) - SI(1), SI(8), SI(5), ch, cg, cb);
|
||
break;
|
||
case UT_INFANTRY:
|
||
case UT_MUTANT:
|
||
disc(sx + SI(1), sy + SI(2), SI(6), ok, og, ob);
|
||
disc(sx, sy, SI(5), dr, dg, db);
|
||
disc(sx - SI(3), sy + SI(4), SI(3), ch, cg, cb); /* crumpled kit */
|
||
disc(sx + SI(4), sy + SI(3), SI(2), 40, 36, 30);
|
||
break;
|
||
default:
|
||
fill_rect(sx - SI(10), sy - SI(6), SI(20), SI(12), ok, og, ob);
|
||
fill_rect(sx - SI(8), sy - SI(4), SI(16), SI(8), dr, dg, db);
|
||
disc(sx - SI(5), sy + SI(2), SI(3), ch, cg, cb);
|
||
disc(sx + SI(6), sy - SI(1), SI(3), ch, cg, cb);
|
||
break;
|
||
}
|
||
/* smoke puff cue */
|
||
disc(sx + SI(3), sy - SI(8), SI(3), 70, 68, 64);
|
||
disc(sx + SI(5), sy - SI(12), SI(2), 90, 88, 82);
|
||
#undef LX
|
||
#undef LY
|
||
#undef SI
|
||
}
|
||
|
||
/* Building silhouettes: bright pad + faction fill, thin black outline. */
|
||
static void draw_bld(Building *b, int sx, int sy) {
|
||
int W = b->w * TILE, H = b->h * TILE;
|
||
uint8_t br = faction_r(b->faction), bg = faction_g(b->faction), bb = faction_b(b->faction);
|
||
uint8_t lr, lg, lb;
|
||
lighten3(br, bg, bb, 0.35f, &lr, &lg, &lb);
|
||
uint8_t ok = 22, og = 20, ob = 16;
|
||
uint8_t wall = 205, wall2 = 175;
|
||
|
||
/* Bright concrete pad on sand */
|
||
fill_rect(sx - 2, sy - 2, W + 4, H + 4, ok, og, ob);
|
||
fill_rect(sx, sy, W, H, 210, 185, 130);
|
||
fill_rect(sx + 2, sy + 2, W - 4, H - 4, wall2, wall2 - 18, wall2 - 45);
|
||
fill_rect(sx + 3, sy + 3, W - 6, H - 6, wall, wall - 22, wall - 55);
|
||
fill_rect(sx + 3, sy + 3, W - 6, 6, wall + 30, wall + 10, wall - 20);
|
||
|
||
switch (b->type) {
|
||
case BT_BASE:
|
||
if (b->faction == FACTION_EVOLVED) {
|
||
/* organic lobes + dark central pit (mutant hive) */
|
||
disc(sx + 14, sy + 18, 14, ok, og, ob);
|
||
disc(sx + 14, sy + 18, 12, br, bg, bb);
|
||
disc(sx + W - 14, sy + 18, 14, ok, og, ob);
|
||
disc(sx + W - 14, sy + 18, 12, br, bg, bb);
|
||
disc(sx + 16, sy + H - 16, 12, ok, og, ob);
|
||
disc(sx + 16, sy + H - 16, 10, br, bg, bb);
|
||
disc(sx + W - 16, sy + H - 16, 12, ok, og, ob);
|
||
disc(sx + W - 16, sy + H - 16, 10, br, bg, bb);
|
||
disc(sx + W/2, sy + H/2, 16, ok, og, ob);
|
||
disc(sx + W/2, sy + H/2, 14, br, bg, bb);
|
||
disc(sx + W/2, sy + H/2, 8, 28, 18, 22); /* dark pit */
|
||
disc(sx + W/2, sy + H/2, 4, 60, 20, 30);
|
||
disc(sx + W/2 - 10, sy + 10, 5, lr, lg, lb); /* blister */
|
||
disc(sx + W/2 + 12, sy + H - 12, 4, lr, lg, lb);
|
||
} else {
|
||
/* Survivors: lime body + corner towers + central mast */
|
||
fill_rect(sx + 4, sy + 4, 14, 14, ok, og, ob); /* NW tower */
|
||
fill_rect(sx + 6, sy + 6, 10, 10, br, bg, bb);
|
||
fill_rect(sx + W - 18, sy + 4, 14, 14, ok, og, ob); /* NE */
|
||
fill_rect(sx + W - 16, sy + 6, 10, 10, br, bg, bb);
|
||
fill_rect(sx + 4, sy + H - 18, 14, 14, ok, og, ob); /* SW */
|
||
fill_rect(sx + 6, sy + H - 16, 10, 10, br, bg, bb);
|
||
fill_rect(sx + W - 18, sy + H - 18, 14, 14, ok, og, ob); /* SE */
|
||
fill_rect(sx + W - 16, sy + H - 16, 10, 10, br, bg, bb);
|
||
fill_rect(sx + 14, sy + 14, W - 28, H - 28, ok, og, ob);
|
||
fill_rect(sx + 16, sy + 16, W - 32, H - 32, br, bg, bb);
|
||
fill_rect(sx + 18, sy + 18, W - 36, 8, lr, lg, lb); /* roof band */
|
||
disc(sx + W/2, sy + H/2 - 2, 10, ok, og, ob);
|
||
disc(sx + W/2, sy + H/2 - 2, 8, lr, lg, lb);
|
||
disc(sx + W/2, sy + H/2 - 2, 4, 255, 250, 180); /* command dome */
|
||
fill_rect(sx + W/2 - 2, sy + 2, 4, H/2 - 6, 200, 180, 60); /* mast */
|
||
fill_rect(sx + W/2 - 6, sy + 2, 12, 3, 220, 210, 80); /* antenna */
|
||
fill_rect(sx + W/2 - 6, sy + H - 12, 12, 6, 40, 38, 34); /* entry */
|
||
}
|
||
break;
|
||
case BT_WARFACTORY:
|
||
/* hangar shell + ridge vents + dark bay door */
|
||
fill_rect(sx + 4, sy + 6, W - 8, H - 12, ok, og, ob);
|
||
fill_rect(sx + 6, sy + 8, W - 12, H - 16, br, bg, bb);
|
||
fill_rect(sx + 8, sy + 10, W - 16, 10, lr, lg, lb); /* roof ridge */
|
||
fill_rect(sx + 10, sy + 12, 6, 6, 55, 50, 42); /* vent */
|
||
fill_rect(sx + W/2 - 3, sy + 12, 6, 6, 55, 50, 42);
|
||
fill_rect(sx + W - 16, sy + 12, 6, 6, 55, 50, 42);
|
||
fill_rect(sx + 10, sy + H - 28, W - 20, 18, ok, og, ob);
|
||
fill_rect(sx + 12, sy + H - 26, W - 24, 14, 35, 35, 40); /* bay */
|
||
line(sx + W/2, sy + H - 26, sx + W/2, sy + H - 12, 70, 68, 60);
|
||
fill_rect(sx + 8, sy + H - 10, W - 16, 4, 50, 48, 42); /* ramp lip */
|
||
break;
|
||
case BT_REFINERY:
|
||
/* latticed derrick tower + gantry + hopper + scrap tank (KKND oil-field read) */
|
||
fill_rect(sx + 6, sy + H - 14, W - 12, 10, ok, og, ob); /* slab */
|
||
fill_rect(sx + 8, sy + H - 12, W - 16, 6, 70, 62, 48);
|
||
/* derrick mast + lattice */
|
||
fill_rect(sx + W/2 - 3, sy + 4, 6, H - 18, ok, og, ob);
|
||
fill_rect(sx + W/2 - 2, sy + 5, 4, H - 20, 95, 85, 65);
|
||
line(sx + W/2 - 6, sy + 10, sx + W/2 + 6, sy + 22, 55, 48, 38);
|
||
line(sx + W/2 + 6, sy + 10, sx + W/2 - 6, sy + 22, 55, 48, 38);
|
||
line(sx + W/2 - 6, sy + 24, sx + W/2 + 6, sy + 36, 55, 48, 38);
|
||
line(sx + W/2 + 6, sy + 24, sx + W/2 - 6, sy + 36, 55, 48, 38);
|
||
line(sx + W/2 - 5, sy + 8, sx + W/2 - 5, sy + H - 16, 40, 36, 30);
|
||
line(sx + W/2 + 5, sy + 8, sx + W/2 + 5, sy + H - 16, 40, 36, 30);
|
||
fill_rect(sx + W/2 - 7, sy + 2, 14, 5, ok, og, ob); /* crown */
|
||
fill_rect(sx + W/2 - 5, sy + 3, 10, 3, 200, 170, 80); /* beacon */
|
||
/* gantry arm toward hopper */
|
||
fill_rect(sx + W/2 + 2, sy + 14, W/2 - 10, 4, ok, og, ob);
|
||
fill_rect(sx + W/2 + 3, sy + 15, W/2 - 12, 2, 120, 100, 60);
|
||
line(sx + W/2 + 4, sy + 18, sx + W - 12, sy + H - 22, 90, 75, 50);
|
||
/* hopper / processing shed */
|
||
fill_rect(sx + W - 22, sy + H - 30, 16, 16, ok, og, ob);
|
||
fill_rect(sx + W - 20, sy + H - 28, 12, 12, br, bg, bb);
|
||
fill_rect(sx + W - 18, sy + H - 26, 8, 6, 140, 110, 55);
|
||
fill_rect(sx + W - 16, sy + H - 18, 4, 4, 40, 36, 30); /* chute */
|
||
/* scrap tank (left) */
|
||
disc(sx + 14, sy + H - 20, 10, ok, og, ob);
|
||
disc(sx + 14, sy + H - 20, 8, 200, 165, 85);
|
||
ring(sx + 14, sy + H - 20, 5, 2, 90, 70, 45);
|
||
line(sx + 20, sy + H - 18, sx + W/2 - 4, sy + H - 14, 110, 90, 50);
|
||
break;
|
||
case BT_POWER:
|
||
/* cooling towers + reactor core + steam cross */
|
||
disc(sx + W/2 - 6, sy + H/2 + 2, 8, ok, og, ob);
|
||
disc(sx + W/2 - 6, sy + H/2 + 2, 6, 100, 160, 210);
|
||
disc(sx + W/2 + 6, sy + H/2 + 2, 8, ok, og, ob);
|
||
disc(sx + W/2 + 6, sy + H/2 + 2, 6, 90, 150, 200);
|
||
fill_rect(sx + 6, sy + 6, W - 12, 10, ok, og, ob);
|
||
fill_rect(sx + 8, sy + 7, W - 16, 7, 70, 120, 170); /* reactor slab */
|
||
disc(sx + W/2, sy + 10, 4, 250, 252, 255);
|
||
line(sx + W/2, sy + 4, sx + W/2, sy + H - 4, 230, 245, 255);
|
||
line(sx + 4, sy + H/2, sx + W - 4, sy + H/2, 200, 230, 250);
|
||
fill_rect(sx + W/2 - 2, sy + H - 8, 4, 5, 55, 50, 42); /* base stub */
|
||
break;
|
||
case BT_TURRET:
|
||
/* bunker base + cupola + thick barrel */
|
||
fill_rect(sx + 3, sy + H - 12, W - 6, 10, ok, og, ob);
|
||
fill_rect(sx + 5, sy + H - 10, W - 10, 7, 55, 50, 42);
|
||
disc(sx + W/2, sy + H/2 + 1, W/3 + 2, ok, og, ob);
|
||
disc(sx + W/2, sy + H/2 + 1, W/3, br, bg, bb);
|
||
disc(sx + W/2, sy + H/2 + 1, W/5, lr, lg, lb);
|
||
disc(sx + W/2, sy + H/2 - 1, 3, 40, 38, 32); /* hatch */
|
||
line(sx + W/2, sy + H/2, sx + W/2 + 13, sy + H/2 - 12, 30, 28, 24);
|
||
line(sx + W/2 + 1, sy + H/2 + 1, sx + W/2 + 13, sy + H/2 - 11, 50, 45, 35);
|
||
disc(sx + W/2 + 12, sy + H/2 - 11, 2, 255, 200, 80);
|
||
break;
|
||
case BT_WALL:
|
||
/* battlement blocks + ridge + shadow seam */
|
||
fill_rect(sx + 1, sy + 1, W - 2, H - 2, ok, og, ob);
|
||
fill_rect(sx + 2, sy + 6, W - 4, H - 8, 175, 155, 120);
|
||
fill_rect(sx + 2, sy + 2, 7, 8, 195, 175, 140); /* merlon */
|
||
fill_rect(sx + 12, sy + 2, 7, 8, 195, 175, 140);
|
||
fill_rect(sx + 22, sy + 2, 7, 8, 195, 175, 140);
|
||
fill_rect(sx + 3, sy + 10, W - 6, 4, 210, 190, 150); /* ridge */
|
||
line(sx + 2, sy + H/2, sx + W - 2, sy + H/2, 60, 52, 42);
|
||
line(sx + W/2, sy + 6, sx + W/2, sy + H - 2, 70, 60, 48);
|
||
break;
|
||
case BT_REPAIR:
|
||
/* garage bay + crane arm + green service mark */
|
||
fill_rect(sx + 5, sy + 8, W - 10, H - 14, ok, og, ob);
|
||
fill_rect(sx + 7, sy + 10, W - 14, H - 18, br, bg, bb);
|
||
fill_rect(sx + 10, sy + H - 26, W - 20, 14, 40, 38, 34); /* bay floor */
|
||
fill_rect(sx + 12, sy + H - 24, W - 24, 10, 55, 52, 48);
|
||
fill_rect(sx + W - 18, sy + 8, 10, 6, ok, og, ob); /* crane mast */
|
||
fill_rect(sx + W - 16, sy + 10, 6, 22, 90, 85, 70);
|
||
line(sx + W - 13, sy + 12, sx + 14, sy + 18, 120, 110, 90); /* jib */
|
||
line(sx + W - 13, sy + 13, sx + 14, sy + 19, 100, 90, 70);
|
||
ring(sx + W/2 - 4, sy + H/2 + 2, 8, 2, 120, 220, 120);
|
||
line(sx + W/2 - 10, sy + H/2 + 2, sx + W/2 + 2, sy + H/2 + 2, 120, 220, 120);
|
||
line(sx + W/2 - 4, sy + H/2 - 4, sx + W/2 - 4, sy + H/2 + 8, 120, 220, 120);
|
||
break;
|
||
case BT_RESEARCH:
|
||
/* lab block + dish + instrument stubs */
|
||
fill_rect(sx + 6, sy + 14, W - 12, H - 20, ok, og, ob);
|
||
fill_rect(sx + 8, sy + 16, W - 16, H - 24, br, bg, bb);
|
||
fill_rect(sx + 10, sy + 18, W - 20, 8, lr, lg, lb);
|
||
disc(sx + W/2 + 6, sy + 14, 12, ok, og, ob);
|
||
disc(sx + W/2 + 6, sy + 14, 10, 210, 180, 80); /* dish */
|
||
ring(sx + W/2 + 6, sy + 14, 7, 2, 180, 150, 60);
|
||
disc(sx + W/2 + 6, sy + 14, 3, 255, 240, 160);
|
||
fill_rect(sx + 12, sy + H - 18, 6, 8, 230, 230, 160); /* stub */
|
||
fill_rect(sx + 22, sy + H - 20, 5, 10, 230, 230, 160);
|
||
line(sx + W/2 + 6, sy + 4, sx + W/2 + 6, sy + 14, 200, 190, 120);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
/* construction scaffold: yard posts/rails/braces (not flat white wash) */
|
||
if (b->build < 1.0f) {
|
||
int mid = sy + (int)(H * b->build);
|
||
if (mid > sy + 2)
|
||
fill_rect(sx + 2, sy + 2, W - 4, mid - sy - 2, 235, 232, 220);
|
||
line(sx, sy, sx, sy + H, 175, 145, 85);
|
||
line(sx + W - 1, sy, sx + W - 1, sy + H, 175, 145, 85);
|
||
line(sx, sy, sx + W, sy, 205, 175, 105);
|
||
line(sx, mid, sx + W, mid, 215, 185, 115);
|
||
line(sx, sy + H - 1, sx + W, sy + H - 1, 155, 125, 70);
|
||
line(sx + 2, sy + 2, sx + W - 2, sy + H - 2, 150, 120, 65);
|
||
line(sx + W - 2, sy + 2, sx + 2, sy + H - 2, 150, 120, 65);
|
||
if (W >= 48)
|
||
line(sx + W / 2, sy, sx + W / 2, sy + H, 165, 135, 75);
|
||
/* progress tick above pad */
|
||
fill_rect(sx, sy - 7, W, 4, 55, 50, 40);
|
||
fill_rect(sx, sy - 7, (int)(W * b->build + 0.5f), 4, 120, 220, 120);
|
||
}
|
||
/* battle damage: scorch patches + crack lines + rubble chips */
|
||
if (b->max_hp > 0 && b->hp < b->max_hp * 0.95f) {
|
||
float dmg = 1.0f - (b->hp / b->max_hp);
|
||
int pw = W / 4; if (pw < 3) pw = 3;
|
||
int ph = H / 5; if (ph < 2) ph = 2;
|
||
fill_rect(sx + W / 4, sy + H / 3, pw, ph, 45, 35, 25);
|
||
if (dmg > 0.3f)
|
||
fill_rect(sx + W / 2, sy + H / 2, pw, ph, 55, 40, 28);
|
||
line(sx + 3, sy + H / 4, sx + W / 2, sy + (H * 2) / 3, 28, 24, 18);
|
||
line(sx + W / 2, sy + (H * 2) / 3, sx + W - 3, sy + H / 3, 28, 24, 18);
|
||
if (dmg > 0.5f) {
|
||
line(sx + W / 3, sy + 2, sx + W / 3 + 3, sy + H - 2, 22, 18, 14);
|
||
fill_rect(sx + 2, sy + H - 4, 5, 3, 95, 78, 55);
|
||
fill_rect(sx + W - 9, sy + H - 5, 7, 4, 85, 70, 50);
|
||
}
|
||
/* thin HP bar so damage reads in KKND_SHOT */
|
||
{
|
||
int by = sy - 6;
|
||
float f = b->hp / b->max_hp;
|
||
fill_rect(sx - 1, by - 1, W + 2, 5, 20, 18, 14);
|
||
fill_rect(sx, by, W, 3, 55, 48, 36);
|
||
fill_rect(sx, by, (int)(W * f + 0.5f), 3, br, bg, 40);
|
||
}
|
||
}
|
||
}
|
||
|
||
void snapshot_capture(Game *g, const char *path) {
|
||
const int zoom = SHOT_ZOOM;
|
||
g_sw = SW / zoom;
|
||
g_sh = SH / zoom;
|
||
if (g_sw < 1) g_sw = 1;
|
||
if (g_sh < 1) g_sh = 1;
|
||
|
||
uint8_t *small = (uint8_t *)malloc((size_t)g_sw * g_sh * 4);
|
||
if (!small) return;
|
||
g_buf = small;
|
||
for (int i = 0; i < g_sw * g_sh; i++) {
|
||
small[i*4]=0; small[i*4+1]=0; small[i*4+2]=0; small[i*4+3]=255;
|
||
}
|
||
|
||
/* Lock camera on the expected raid locus (hx+88, hy-48). Mild bias toward
|
||
* living enemies still inside the clash radius — never mid-map garrison. */
|
||
float hx = 0, hy = 0;
|
||
{
|
||
int sx = g->map.spawn_x[g->human], sy = g->map.spawn_y[g->human];
|
||
map_tile_to_world(sx, sy, &hx, &hy);
|
||
}
|
||
float clash_x = hx + 88.0f, clash_y = hy - 48.0f;
|
||
const float clash_r2 = 220.0f * 220.0f;
|
||
|
||
float ex = 0, ey = 0;
|
||
int ne = 0;
|
||
for (int i = 0; i < g->unit_count; i++) {
|
||
Unit *u = &g->units[i];
|
||
if (u->dead || u->faction == g->human) continue;
|
||
float dx = u->x - clash_x, dy = u->y - clash_y;
|
||
if (dx * dx + dy * dy > clash_r2) continue;
|
||
ex += u->x; ey += u->y; ne++;
|
||
}
|
||
float ecx = ne ? ex / ne : clash_x;
|
||
float ecy = ne ? ey / ne : clash_y;
|
||
|
||
/* Clash + forward fight; stronger hx so Survivor forward HQ stays in crop. */
|
||
float cx = clash_x * 0.40f + ecx * 0.10f + hx * 0.50f;
|
||
float cy = clash_y * 0.40f + ecy * 0.10f + hy * 0.50f;
|
||
|
||
float camx = cx - g_sw / 2.0f, camy = cy - g_sh / 2.0f;
|
||
if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||
if (camx > MAP_PIX_W - g_sw) camx = (float)(MAP_PIX_W - g_sw);
|
||
if (camy > MAP_PIX_H - g_sh) camy = (float)(MAP_PIX_H - g_sh);
|
||
if (camx < 0) camx = 0; if (camy < 0) camy = 0;
|
||
|
||
int in_u = 0, in_b = 0, alive_e = 0, alive_h = 0;
|
||
for (int i = 0; i < g->unit_count; i++) {
|
||
Unit *u = &g->units[i];
|
||
if (u->dead) continue;
|
||
if (u->faction == g->human) alive_h++; else alive_e++;
|
||
int sx = (int)(u->x - camx), sy = (int)(u->y - camy);
|
||
if (sx >= -16 && sy >= -16 && sx < g_sw + 16 && sy < g_sh + 16) in_u++;
|
||
}
|
||
for (int i = 0; i < g->bld_count; i++) {
|
||
Building *b = &g->buildings[i];
|
||
if (b->dead) continue;
|
||
int sx = (int)(b->tx * TILE - camx), sy = (int)(b->ty * TILE - camy);
|
||
int w = b->w * TILE, h = b->h * TILE;
|
||
if (sx + w >= 0 && sy + h >= 0 && sx < g_sw && sy < g_sh) in_b++;
|
||
}
|
||
engine_log("shot cam=(%.0f,%.0f) crop=%dx%d clash_enemies=%d "
|
||
"in_frame units=%d blds=%d alive H/E=%d/%d",
|
||
camx, camy, g_sw, g_sh, ne, in_u, in_b, alive_h, alive_e);
|
||
|
||
/* terrain */
|
||
int tx0 = (int)camx / TILE, ty0 = (int)camy / TILE;
|
||
int tx1 = (int)(camx + g_sw) / TILE, ty1 = (int)(camy + g_sh) / TILE;
|
||
for (int ty = ty0; ty <= ty1; ty++) {
|
||
for (int tx = tx0; tx <= tx1; tx++) {
|
||
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) continue;
|
||
Tile *t = map_tile(&g->map, tx, ty);
|
||
uint8_t r,gv,b; terrain_color(t->terrain, &r,&gv,&b);
|
||
/* Mild checker — keep yellow bias (nudge R/G, never push B). */
|
||
if (((tx + ty) & 1) == 0) {
|
||
if (r < 250) r += 8; if (gv < 240) gv += 4;
|
||
} else {
|
||
if (r > 10) r -= 4; if (gv > 10) gv -= 3; if (b > 6) b -= 2;
|
||
}
|
||
int sx = tx*TILE - (int)camx, sy = ty*TILE - (int)camy;
|
||
rectf(sx, sy, sx+TILE-1, sy+TILE-1, r, gv, b);
|
||
/* Crack / grit lines so desert reads as KKND2 dirt, not flat fill. */
|
||
if (t->terrain == T_GRASS || t->terrain == T_SCRAP) {
|
||
line(sx + 4, sy + 10, sx + 14, sy + 18, (uint8_t)(r - 28), (uint8_t)(gv - 24), (uint8_t)(b - 12));
|
||
line(sx + 18, sy + 6, sx + 28, sy + 14, (uint8_t)(r - 22), (uint8_t)(gv - 18), (uint8_t)(b - 10));
|
||
disc(sx + 9, sy + 22, 1, (uint8_t)(r - 35), (uint8_t)(gv - 30), (uint8_t)(b - 15));
|
||
disc(sx + 24, sy + 26, 1, (uint8_t)(r - 30), (uint8_t)(gv - 26), (uint8_t)(b - 12));
|
||
}
|
||
if (t->terrain == T_RUBBLE) {
|
||
disc(sx + 8, sy + 10, 3, 120, 95, 60);
|
||
disc(sx + 22, sy + 18, 3, 138, 110, 70);
|
||
disc(sx + 14, sy + 24, 2, 105, 85, 55);
|
||
disc(sx + 26, sy + 8, 2, 155, 125, 80);
|
||
}
|
||
if (t->terrain == T_ROAD) {
|
||
fill_rect(sx + 3, sy + TILE/2 - 3, TILE - 6, 6, 195, 165, 105);
|
||
line(sx + 2, sy + TILE/2, sx + TILE - 3, sy + TILE/2, 225, 195, 130);
|
||
line(sx + TILE/2, sy + 2, sx + TILE/2, sy + TILE - 3, 165, 140, 95);
|
||
}
|
||
if (t->terrain == T_SCRAP && t->scrap > 0) {
|
||
/* stacked scrap mounds (KKND2 resource piles) */
|
||
int cc = sx + TILE / 2, cr = sy + TILE / 2 + 2;
|
||
int n = 3 + t->scrap / 28;
|
||
if (n > 9) n = 9;
|
||
for (int k = 0; k < n; k++) {
|
||
int ox = ((k * 7) % 14) - 7;
|
||
int oy = ((k * 5) % 10) - 5 - (k / 3) * 2;
|
||
int rad = 4 + (k & 1) + (t->scrap > 120 ? 1 : 0);
|
||
disc(cc + ox, cr + oy, rad, 235, 190, 85);
|
||
disc(cc + ox - 1, cr + oy - rad / 2, rad - 1, 210, 165, 70);
|
||
disc(cc + ox + 1, cr + oy - rad + 1, (rad > 3) ? rad - 2 : 2,
|
||
255, 215, 120);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Showcase shot: force-clear fog in the camera crop so buildings/units
|
||
* read like a playable KKND2 battle view (not a black fog plate). */
|
||
for (int ty = ty0; ty <= ty1; ty++)
|
||
for (int tx = tx0; tx <= tx1; tx++) {
|
||
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) continue;
|
||
g->map.fog[ty * MAP_W + tx] = 2;
|
||
}
|
||
|
||
/* Soft edge only outside live vision (kept for minimap consistency). */
|
||
for (int ty = ty0; ty <= ty1; ty++)
|
||
for (int tx = tx0; tx <= tx1; tx++) {
|
||
if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_H) continue;
|
||
int f = g->map.fog[ty*MAP_W + tx];
|
||
if (f >= 2) continue;
|
||
float keep = (f == 1) ? 0.70f : 0.45f;
|
||
int sx = tx*TILE - (int)camx, sy = ty*TILE - (int)camy;
|
||
for (int yy = sy; yy < sy + TILE; yy++)
|
||
for (int xx = sx; xx < sx + TILE; xx++)
|
||
darken_px(xx, yy, keep);
|
||
}
|
||
|
||
/* buildings */
|
||
for (int i = 0; i < g->bld_count; i++) {
|
||
Building *b = &g->buildings[i];
|
||
if (b->dead) continue;
|
||
int sx = (int)(b->tx*TILE - camx), sy = (int)(b->ty*TILE - camy);
|
||
int w = b->w*TILE, h = b->h*TILE;
|
||
if (sx+w < 0 || sy+h < 0 || sx > g_sw || sy > g_sh) continue;
|
||
draw_bld(b, sx, sy);
|
||
}
|
||
|
||
/* wrecks under living units (battlefield litter) */
|
||
for (int i = 0; i < g->wreck_count; i++) {
|
||
Wreck *w = &g->wrecks[i];
|
||
int sx = (int)(w->x - camx), sy = (int)(w->y - camy);
|
||
if (sx < -24 || sy < -24 || sx > g_sw + 24 || sy > g_sh + 24) continue;
|
||
draw_wreck(w, sx, sy);
|
||
}
|
||
|
||
/* units */
|
||
for (int i = 0; i < g->unit_count; i++) {
|
||
Unit *u = &g->units[i];
|
||
if (u->dead) continue;
|
||
int sx = (int)(u->x - camx), sy = (int)(u->y - camy);
|
||
if (sx < -20 || sy < -20 || sx > g_sw+20 || sy > g_sh+20) continue;
|
||
draw_unit(u, sx, sy);
|
||
}
|
||
|
||
/* projectiles — bright tracers / shells with streak tails (KKND2-like) */
|
||
for (int i = 0; i < g->bullet_count; i++) {
|
||
Bullet *p = &g->bullets[i];
|
||
int sx = (int)(p->x - camx), sy = (int)(p->y - camy);
|
||
float dx = p->tx - p->x, dy = p->ty - p->y;
|
||
float d = sqrtf(dx * dx + dy * dy);
|
||
if (d > 1.0f) {
|
||
float ux = dx / d, uy = dy / d;
|
||
int len = (p->kind == 2) ? 32 : (p->kind == 3) ? 24
|
||
: (p->kind == 1) ? 20 : 14;
|
||
for (int t = 1; t <= len; t++) {
|
||
int tx = sx - (int)(ux * (float)t * 0.75f);
|
||
int ty = sy - (int)(uy * (float)t * 0.75f);
|
||
uint8_t fade = (uint8_t)(255 - t * 9);
|
||
if (p->kind == 2) {
|
||
/* laser beam: cyan→magenta streak */
|
||
disc(tx, ty, (t < 3) ? 2 : 1, fade, 80, 255);
|
||
px(tx, ty, 220, 180, 255);
|
||
} else if (p->kind == 3) {
|
||
disc(tx, ty, (t < 4) ? 2 : 1, 255, (uint8_t)(120 - t * 4), 30);
|
||
} else {
|
||
disc(tx, ty, 1, 255, (uint8_t)(200 - t * 8), 50);
|
||
}
|
||
}
|
||
}
|
||
int rad = (p->kind == 1 || p->kind == 3) ? 8 : 5;
|
||
disc(sx, sy, rad + 1, 255, 160, 40);
|
||
disc(sx, sy, rad, 255, 240, 140);
|
||
px(sx + 1, sy, 255, 255, 220);
|
||
if (p->kind == 3) {
|
||
disc(sx - 3, sy + 1, 4, 255, 70, 30);
|
||
disc(sx + 2, sy - 2, 3, 255, 200, 80);
|
||
} else if (p->kind == 2) {
|
||
disc(sx, sy, rad + 1, 120, 200, 255);
|
||
disc(sx, sy, rad - 1, 240, 220, 255);
|
||
}
|
||
}
|
||
|
||
/* particles (smoke / fire / sparks / debris) — larger combat FX */
|
||
for (int i = 0; i < g->particle_count; i++) {
|
||
Particle *p = &g->particles[i];
|
||
if (p->life <= 0) continue;
|
||
int sx = (int)(p->x - camx), sy = (int)(p->y - camy);
|
||
if (sx < -12 || sy < -12 || sx > g_sw + 12 || sy > g_sh + 12) continue;
|
||
float a = p->life / (p->max_life > 0 ? p->max_life : 1.0f);
|
||
if (a < 0.10f) continue;
|
||
int sz = (p->kind == 1) ? 9 : (p->kind == 3) ? 5 : (p->kind == 0) ? 7 : 4;
|
||
uint8_t r = (uint8_t)(p->r * a), gv = (uint8_t)(p->g * a), b = (uint8_t)(p->b * a);
|
||
if (p->kind == 0 || p->kind == 1) {
|
||
disc(sx, sy, sz + 2, r / 2, gv / 2, b / 2);
|
||
disc(sx, sy, sz + 1, r, gv, b);
|
||
if (p->kind == 1 && a > 0.45f)
|
||
disc(sx, sy - 1, sz / 2 + 1, 255, (uint8_t)(220 * a), 80);
|
||
} else {
|
||
fill_rect(sx - sz/2, sy - sz/2, sz + 1, sz + 1, r, gv, b);
|
||
}
|
||
}
|
||
/* KKND2-like chrome: top resource bar + corner minimap + bottom command strip.
|
||
* Procedural bars (no font) so the shot still reads as classic RTS UI. */
|
||
{
|
||
Faction hf = g->human;
|
||
uint8_t hr = faction_r(hf), hg = faction_g(hf), hb = faction_b(hf);
|
||
Faction ef = enemy_of(hf);
|
||
uint8_t er = faction_r(ef), eg = faction_g(ef), eb = faction_b(ef);
|
||
|
||
int scrap_h = g->scrap[hf];
|
||
if (scrap_h < 0) scrap_h = 0;
|
||
int scrap_cap = 8000;
|
||
if (scrap_h > scrap_cap) scrap_cap = scrap_h + 500;
|
||
float scrap_frac = (float)scrap_h / (float)scrap_cap;
|
||
if (scrap_frac > 1.0f) scrap_frac = 1.0f;
|
||
|
||
int pwr_gen = 0, pwr_use = 0;
|
||
for (int i = 0; i < g->bld_count; i++) {
|
||
Building *b = &g->buildings[i];
|
||
if (b->dead || b->faction != hf || b->build < 1.0f) continue;
|
||
if (b->type == BT_POWER) pwr_gen += 100;
|
||
else if (b->type == BT_BASE) pwr_use += 20;
|
||
else if (b->type == BT_WARFACTORY || b->type == BT_REFINERY) pwr_use += 40;
|
||
else if (b->type == BT_RESEARCH || b->type == BT_REPAIR) pwr_use += 30;
|
||
else if (b->type == BT_TURRET) pwr_use += 15;
|
||
else pwr_use += 5;
|
||
}
|
||
if (pwr_gen < 1) pwr_gen = 1;
|
||
float pwr_frac = (float)pwr_gen / (float)(pwr_gen + pwr_use);
|
||
if (pwr_frac > 1.0f) pwr_frac = 1.0f;
|
||
int pwr_ok = (pwr_gen >= pwr_use);
|
||
|
||
int n_sel_u = 0, n_sel_b = 0;
|
||
for (int i = 0; i < g->sel_count; i++) {
|
||
if (unit_by_id(g, g->selection[i])) n_sel_u++;
|
||
else if (bld_by_id(g, g->selection[i])) n_sel_b++;
|
||
}
|
||
|
||
/* —— top bar —— */
|
||
int bar_h = 22;
|
||
fill_rect(0, 0, g_sw, bar_h, 22, 26, 32);
|
||
fill_rect(0, 0, g_sw, 2, 55, 62, 70);
|
||
fill_rect(0, bar_h - 1, g_sw, 1, 90, 110, 70);
|
||
fill_rect(0, bar_h, g_sw, 1, 40, 48, 40);
|
||
|
||
/* faction badge */
|
||
fill_rect(3, 3, 16, 16, 12, 14, 16);
|
||
fill_rect(5, 5, 12, 12, hr, hg, hb);
|
||
fill_rect(7, 7, 8, 8, (uint8_t)(hr / 2 + 40), (uint8_t)(hg / 2 + 40),
|
||
(uint8_t)(hb / 2 + 40));
|
||
|
||
/* scrap icon (gold nugget) + segmented bar */
|
||
fill_rect(24, 5, 8, 12, 40, 34, 18);
|
||
fill_rect(26, 7, 4, 8, 220, 190, 70);
|
||
disc(28, 11, 3, 255, 220, 90);
|
||
int scrap_x = 36, scrap_w = (int)(g_sw * 0.28f);
|
||
if (scrap_w < 60) scrap_w = 60;
|
||
fill_rect(scrap_x, 6, scrap_w, 10, 28, 26, 18);
|
||
fill_rect(scrap_x, 6, (int)(scrap_w * scrap_frac), 10, 230, 200, 70);
|
||
/* segment ticks */
|
||
for (int s = 1; s < 8; s++) {
|
||
int sx = scrap_x + (scrap_w * s) / 8;
|
||
line(sx, 6, sx, 15, 18, 16, 12);
|
||
}
|
||
fill_rect(scrap_x, 6, scrap_w, 1, 255, 240, 160);
|
||
fill_rect(scrap_x, 15, scrap_w, 1, 80, 60, 20);
|
||
|
||
/* power bolt + bar (green surplus / red deficit) */
|
||
int pwr_x = scrap_x + scrap_w + 10;
|
||
fill_rect(pwr_x, 5, 3, 5, 240, 240, 120);
|
||
fill_rect(pwr_x - 1, 9, 5, 3, 240, 240, 120);
|
||
fill_rect(pwr_x + 1, 11, 3, 5, 240, 240, 120);
|
||
int pwr_bx = pwr_x + 10, pwr_bw = (int)(g_sw * 0.16f);
|
||
if (pwr_bw < 40) pwr_bw = 40;
|
||
fill_rect(pwr_bx, 6, pwr_bw, 10, 24, 28, 24);
|
||
uint8_t pr = pwr_ok ? 70 : 220, pg = pwr_ok ? 210 : 70, pb = pwr_ok ? 90 : 60;
|
||
fill_rect(pwr_bx, 6, (int)(pwr_bw * pwr_frac), 10, pr, pg, pb);
|
||
for (int s = 1; s < 5; s++) {
|
||
int sx = pwr_bx + (pwr_bw * s) / 5;
|
||
line(sx, 6, sx, 15, 14, 16, 14);
|
||
}
|
||
|
||
/* unit / building count chips */
|
||
int chip_x = pwr_bx + pwr_bw + 12;
|
||
fill_rect(chip_x, 4, 34, 14, 30, 36, 30);
|
||
fill_rect(chip_x + 2, 6, 6, 10, hr, hg, hb); /* unit glyph */
|
||
disc(chip_x + 5, 8, 2, 255, 255, 255);
|
||
int ufill = alive_h;
|
||
if (ufill > 20) ufill = 20;
|
||
fill_rect(chip_x + 12, 8, ufill, 6, hr, hg, hb);
|
||
fill_rect(chip_x + 38, 4, 34, 14, 30, 36, 30);
|
||
fill_rect(chip_x + 40, 7, 8, 8, hr, hg, hb); /* bld glyph */
|
||
int bfill = 0;
|
||
for (int i = 0; i < g->bld_count; i++)
|
||
if (!g->buildings[i].dead && g->buildings[i].faction == hf) bfill++;
|
||
if (bfill > 20) bfill = 20;
|
||
fill_rect(chip_x + 52, 8, bfill, 6, 180, 160, 70);
|
||
|
||
/* enemy threat pip */
|
||
fill_rect(g_sw - 48, 4, 42, 14, 36, 22, 28);
|
||
fill_rect(g_sw - 44, 6, 8, 10, er, eg, eb);
|
||
int eth = alive_e;
|
||
if (eth > 28) eth = 28;
|
||
fill_rect(g_sw - 34, 8, eth, 6, er, eg, eb);
|
||
disc(g_sw - 10, 11, 4, 255, 90, 40);
|
||
disc(g_sw - 10, 11, 2, 255, 220, 120);
|
||
|
||
/* —— corner minimap (compact, fight-plate friendly) —— */
|
||
int mw = 72, mh = 72;
|
||
int mx = g_sw - mw - 6, my = bar_h + 6;
|
||
fill_rect(mx - 2, my - 2, mw + 4, mh + 4, 8, 10, 12);
|
||
fill_rect(mx - 1, my - 1, mw + 2, mh + 2, 70, 90, 70);
|
||
fill_rect(mx, my, mw, mh, 28, 32, 28);
|
||
/* sample terrain around camera */
|
||
float map_sx = (float)mw / (float)g_sw;
|
||
float map_sy = (float)mh / (float)g_sh;
|
||
for (int py = 0; py < mh; py += 2) {
|
||
for (int px_ = 0; px_ < mw; px_ += 2) {
|
||
float wx = camx + (float)px_ / map_sx;
|
||
float wy = camy + (float)py / map_sy;
|
||
int tx = (int)(wx / TILE), ty = (int)(wy / TILE);
|
||
if (!map_in_bounds(&g->map, tx, ty)) continue;
|
||
Tile *t = map_tile(&g->map, tx, ty);
|
||
uint8_t 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 = 210; cg = 170; cb = 78; break;
|
||
case T_RUBBLE: cr = 90; cg = 78; cb = 60; break;
|
||
default: break;
|
||
}
|
||
fill_rect(mx + px_, my + py, 2, 2, cr, cg, cb);
|
||
}
|
||
}
|
||
for (int i = 0; i < g->bld_count; i++) {
|
||
Building *b = &g->buildings[i];
|
||
if (b->dead) continue;
|
||
int bx = mx + (int)((b->tx * TILE - camx) * map_sx);
|
||
int by = my + (int)((b->ty * TILE - camy) * map_sy);
|
||
if (bx < mx || by < my || bx >= mx + mw || by >= my + mh) continue;
|
||
uint8_t br = faction_r(b->faction), bg = faction_g(b->faction),
|
||
bb = faction_b(b->faction);
|
||
fill_rect(bx, by, 3, 3, br, bg, bb);
|
||
}
|
||
for (int i = 0; i < g->unit_count; i++) {
|
||
Unit *u = &g->units[i];
|
||
if (u->dead) continue;
|
||
int ux = mx + (int)((u->x - camx) * map_sx);
|
||
int uy = my + (int)((u->y - camy) * map_sy);
|
||
if (ux < mx || uy < my || ux >= mx + mw || uy >= my + mh) continue;
|
||
px(ux, uy, faction_r(u->faction), faction_g(u->faction),
|
||
faction_b(u->faction));
|
||
}
|
||
/* viewport outline = full minimap (crop view) */
|
||
line(mx, my, mx + mw - 1, my, 255, 255, 255);
|
||
line(mx, my + mh - 1, mx + mw - 1, my + mh - 1, 255, 255, 255);
|
||
line(mx, my, mx, my + mh - 1, 255, 255, 255);
|
||
line(mx + mw - 1, my, mx + mw - 1, my + mh - 1, 255, 255, 255);
|
||
|
||
/* —— bottom command / selection strip —— */
|
||
int bot_h = 28;
|
||
int bot_y = g_sh - bot_h;
|
||
fill_rect(0, bot_y, g_sw, bot_h, 18, 20, 24);
|
||
fill_rect(0, bot_y, g_sw, 1, 70, 90, 70);
|
||
fill_rect(0, bot_y + 1, g_sw, 1, 40, 48, 40);
|
||
/* portrait panel */
|
||
fill_rect(4, bot_y + 3, 22, 22, 12, 14, 16);
|
||
fill_rect(6, bot_y + 5, 18, 18, hr, hg, hb);
|
||
disc(15, bot_y + 12, 4, 255, 255, 255);
|
||
fill_rect(13, bot_y + 16, 4, 5, 40, 40, 40);
|
||
/* selection count bars */
|
||
int slots = (n_sel_u > 0 || n_sel_b > 0) ? (n_sel_u + n_sel_b) : 6;
|
||
if (slots > 10) slots = 10;
|
||
for (int s = 0; s < slots; s++) {
|
||
int sx = 32 + s * 22;
|
||
fill_rect(sx, bot_y + 4, 18, 20, 28, 32, 28);
|
||
fill_rect(sx + 1, bot_y + 5, 16, 18, 40, 48, 40);
|
||
if (s < n_sel_u || (n_sel_u == 0 && n_sel_b == 0 && s < 4)) {
|
||
fill_rect(sx + 4, bot_y + 8, 10, 12, hr, hg, hb);
|
||
disc(sx + 9, bot_y + 11, 2, 255, 255, 255);
|
||
} else if (s < n_sel_u + n_sel_b) {
|
||
fill_rect(sx + 3, bot_y + 8, 12, 12, hr, hg, hb);
|
||
}
|
||
}
|
||
/* ability / build button stubs (right) */
|
||
for (int s = 0; s < 4; s++) {
|
||
int sx = g_sw - 96 + s * 22;
|
||
fill_rect(sx, bot_y + 4, 18, 20, 32, 28, 22);
|
||
fill_rect(sx + 2, bot_y + 6, 14, 16, 70, 60, 40);
|
||
if (s == 0) fill_rect(sx + 6, bot_y + 10, 6, 8, 200, 180, 60); /* build */
|
||
if (s == 1) disc(sx + 9, bot_y + 14, 4, 255, 80, 40); /* attack */
|
||
if (s == 2) fill_rect(sx + 5, bot_y + 11, 8, 6, 80, 180, 255); /* move */
|
||
if (s == 3) ring(sx + 9, bot_y + 14, 5, 2, 180, 255, 120); /* stop */
|
||
}
|
||
}
|
||
|
||
/* Nearest-neighbour upscale → full VIEW (chunky KKND pixels) */
|
||
uint8_t *big = (uint8_t *)malloc((size_t)SW * SH * 4);
|
||
if (!big) { free(small); return; }
|
||
for (int y = 0; y < SH; y++) {
|
||
int sy = y / zoom;
|
||
if (sy >= g_sh) sy = g_sh - 1;
|
||
for (int x = 0; x < SW; x++) {
|
||
int sx = x / zoom;
|
||
if (sx >= g_sw) sx = g_sw - 1;
|
||
int di = (y * SW + x) * 4;
|
||
int si = (sy * g_sw + sx) * 4;
|
||
big[di]=small[si]; big[di+1]=small[si+1];
|
||
big[di+2]=small[si+2]; big[di+3]=255;
|
||
}
|
||
}
|
||
png_save(path, SW, SH, big);
|
||
free(big);
|
||
free(small);
|
||
g_buf = NULL;
|
||
g_sw = SW; g_sh = SH;
|
||
}
|