diff --git a/engine-c/src/render.c b/engine-c/src/render.c index 39eda33..d94a5e6 100644 --- a/engine-c/src/render.c +++ b/engine-c/src/render.c @@ -220,6 +220,88 @@ static void draw_unit_shadow(SDL_Renderer *ren, int cx, int cy, int rad) { } } +static int live_unit_wheeled(UnitType t) { + return t == UT_BUGGY || t == UT_JEEP || t == UT_HARVESTER + || t == UT_BEAST || t == UT_MISSILE || t == UT_MEDIC; +} +static int live_unit_tracked(UnitType t) { + return t == UT_TANK || t == UT_RAVAGER; +} +static int live_unit_rolling(UnitType t) { + return live_unit_wheeled(t) || live_unit_tracked(t); +} + +/* Spoke (wheeled) / hash (tracked) on tyre discs; phase = Unit.anim rad. + * Local 30×30 chassis coords match snapshot LX/LY, scaled to live sz. */ +static void live_tyre_mark(SDL_Renderer *ren, int cx, int cy, int rad, + float phase, int wheeled) { + 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; + SDL_SetRenderDrawColor(ren, 205, 200, 180, 240); + 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); + SDL_RenderDrawLine(ren, x1, y1, x2, y2); + } +} + +static void live_draw_tyre_marks(SDL_Renderer *ren, Unit *u, int cx, int cy, int sz) { + if (!live_unit_rolling(u->type)) return; + float c = cosf(u->heading), s = sinf(u->heading); + float sc = (float)sz / 30.0f; + float ph = u->anim; + #define LTX(lx, ly) (cx + (int)((((lx)-15)*sc)*c - (((ly)-15)*sc)*s)) + #define LTY(lx, ly) (cy + (int)((((lx)-15)*sc)*s + (((ly)-15)*sc)*c)) + #define LRAD(n) ((int)((n) * sc + 0.5f)) + switch (u->type) { + case UT_BUGGY: + live_tyre_mark(ren, LTX(8,7), LTY(8,7), LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(22,7), LTY(22,7), LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(8,23), LTY(8,23), LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(22,23),LTY(22,23),LRAD(4), ph, 1); + break; + case UT_TANK: + live_tyre_mark(ren, LTX(8,23), LTY(8,23), LRAD(4), ph, 0); + live_tyre_mark(ren, LTX(22,23),LTY(22,23),LRAD(4), ph, 0); + break; + case UT_HARVESTER: + live_tyre_mark(ren, LTX(8,23), LTY(8,23), LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(22,23),LTY(22,23),LRAD(4), ph, 1); + break; + case UT_JEEP: + live_tyre_mark(ren, LTX(9,22), LTY(9,22), LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(21,22),LTY(21,22),LRAD(4), ph, 1); + break; + case UT_BEAST: + live_tyre_mark(ren, LTX(9,21), LTY(9,21), LRAD(3), ph, 1); + live_tyre_mark(ren, LTX(14,22),LTY(14,22),LRAD(3), ph, 1); + live_tyre_mark(ren, LTX(19,21),LTY(19,21),LRAD(3), ph, 1); + live_tyre_mark(ren, LTX(23,22),LTY(23,22),LRAD(3), ph, 1); + break; + case UT_RAVAGER: + live_tyre_mark(ren, LTX(7,23), LTY(7,23), LRAD(4), ph, 0); + live_tyre_mark(ren, LTX(23,23),LTY(23,23),LRAD(4), ph, 0); + break; + case UT_MISSILE: + live_tyre_mark(ren, LTX(9,23), LTY(9,23), LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(21,23),LTY(21,23),LRAD(4), ph, 1); + break; + case UT_MEDIC: + live_tyre_mark(ren, LTX(10,24),LTY(10,24),LRAD(4), ph, 1); + live_tyre_mark(ren, LTX(20,24),LTY(20,24),LRAD(4), ph, 1); + break; + default: break; + } + #undef LTX + #undef LTY + #undef LRAD +} + void render_units(Game *g) { for (int i = 0; i < g->unit_count; i++) { Unit *u = &g->units[i]; @@ -234,6 +316,8 @@ void render_units(Game *g) { draw_unit_shadow(g->ren, cx, cy, sz / 4 + 4); SDL_Texture *tex = g->spr.unit[u->type][u->faction]; draw_tex_world(g, tex, u->x, u->y, sz, sz, u->heading, 255); + /* rotating spokes / tread hashes (parity with snapshot tyre_mark) */ + live_draw_tyre_marks(g->ren, u, cx, cy, sz); /* cream heading tick (parity with snapshot LX/LY overlay) */ { float tick = (float)(sz / 2 + 6); diff --git a/engine-c/src/snapshot.c b/engine-c/src/snapshot.c index abadaa4..4260bb5 100644 --- a/engine-c/src/snapshot.c +++ b/engine-c/src/snapshot.c @@ -103,6 +103,23 @@ static void terrain_color(TerrainType t, uint8_t *r, uint8_t *g, uint8_t *b) { } } +/* 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) { @@ -113,6 +130,7 @@ static void draw_unit(Unit *u, int sx, int sy) { 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 phase for wheeled/tracked */ #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)) @@ -135,6 +153,10 @@ static void draw_unit(Unit *u, int sx, int sy) { 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); + 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,15), LY(29,15), 40, 36, 30); break; case UT_TANK: @@ -144,6 +166,8 @@ static void draw_unit(Unit *u, int sx, int sy) { line(sx, sy, LX(30,15), LY(30,15), 50, 45, 35); 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: fill_rect(sx-SI(13), sy-SI(5), SI(26), SI(12), ok, og, ob); @@ -158,6 +182,8 @@ static void draw_unit(Unit *u, int sx, int sy) { fill_rect(sx-SI(10), sy-SI(5), SI(20), SI(10), 200, 170, 90); 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, 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); break; case UT_JEEP: @@ -165,6 +191,8 @@ static void draw_unit(Unit *u, int sx, int sy) { fill_rect(sx-SI(9), sy-SI(4), SI(18), SI(8), br, bg, bb); disc(LX(9,22), LY(9,22), SI(4), mr, mg, mb); disc(LX(21,22), LY(21,22), SI(4), mr, mg, mb); + 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(15,11), LY(15,11), LX(15,3), LY(15,3), lr, lg, lb); line(sx, sy, LX(28,15), LY(28,15), 50, 45, 35); break; @@ -181,6 +209,10 @@ static void draw_unit(Unit *u, int sx, int sy) { 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(23,12), LY(23,12), SI(5), lr, lg, lb); break; case UT_RAVAGER: @@ -190,6 +222,8 @@ static void draw_unit(Unit *u, int sx, int sy) { line(sx, sy, LX(29,15), LY(29,15), 50, 45, 35); 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: fill_rect(sx-SI(10), sy-SI(4), SI(20), SI(15), ok, og, ob); @@ -202,6 +236,8 @@ static void draw_unit(Unit *u, int sx, int sy) { fill_rect(sx-SI(9), sy-SI(2), SI(18), SI(10), br, bg, bb); disc(LX(9,23), LY(9,23), SI(4), mr, mg, mb); disc(LX(21,23), LY(21,23), SI(4), mr, mg, mb); + 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,6), LY(28,6), 255, 80, 60); break; case UT_MEDIC: @@ -209,6 +245,8 @@ static void draw_unit(Unit *u, int sx, int sy) { fill_rect(sx-SI(8), sy-SI(3), SI(16), SI(11), 245, 245, 245); disc(LX(10,24), LY(10,24), SI(4), mr, mg, mb); disc(LX(20,24), LY(20,24), SI(4), mr, mg, mb); + 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(5), 240, 50, 50); fill_rect(LX(14,6)-1, LY(14,6)-SI(4), SI(3), SI(8), 255, 255, 255); fill_rect(LX(11,8)-SI(4), LY(11,8)-1, SI(8), SI(3), 255, 255, 255); diff --git a/engine-c/src/units.c b/engine-c/src/units.c index 40a9a86..4ccc20f 100644 --- a/engine-c/src/units.c +++ b/engine-c/src/units.c @@ -6,6 +6,23 @@ static int unit_alloc_id(Game *g) { return g->unit_next_id++; } +/* Wheeled / tracked chassis: Unit.anim is continuous tyre phase (rad), not a pulse. */ +static int unit_wheeled(UnitType t) { + return t == UT_BUGGY || t == UT_JEEP || t == UT_HARVESTER + || t == UT_BEAST || t == UT_MISSILE || t == UT_MEDIC; +} +static int unit_tracked(UnitType t) { + return t == UT_TANK || t == UT_RAVAGER; +} +static int unit_rolling(UnitType t) { + return unit_wheeled(t) || unit_tracked(t); +} +static void unit_roll_phase(Unit *u, float sp) { + /* ~1 revolution per ~40 world px */ + u->anim = fmodf(u->anim + (sp / 40.0f) * (float)(2.0 * M_PI), (float)(2.0 * M_PI)); + if (u->anim < 0.0f) u->anim += (float)(2.0 * M_PI); +} + Unit *unit_by_id(Game *g, int id) { for (int i = 0; i < g->unit_count; i++) if (!g->units[i].dead && g->units[i].id == id) @@ -336,7 +353,8 @@ static void unit_combat_step(Game *g, Unit *u, float dt) { particle_burst(g, mx, my, 3, 1, 255, 160, 50); u->atk_cd = ((u->type == UT_ARTILLERY) ? 2.2f : (u->type == UT_INFANTRY ? 0.8f : 1.0f)) / u->reload_mul; - u->anim = 1.0f; + /* rolling chassis keep continuous tyre phase — no fire pulse */ + if (!unit_rolling(u->type)) u->anim = 1.0f; audio_sfx(1); } return; @@ -405,7 +423,7 @@ static void unit_harvester_step(Game *g, Unit *u, float dt) { int add = (d->cargo - u->cargo > take) ? take : (int)(d->cargo - u->cargo); u->cargo += add; map_deplete_scrap(&g->map, tx, ty, add); - u->anim = 1.0f; + if (!unit_rolling(u->type)) u->anim = 1.0f; } if (u->cargo >= d->cargo) { int rid = harvester_find_refinery(g, u); @@ -505,7 +523,8 @@ void unit_update(Game *g, float dt) { for (int i = 0; i < g->unit_count; i++) { Unit *u = &g->units[i]; if (u->dead) continue; - if (u->anim > 0) u->anim -= dt * 3; + /* infantry/pulse anim decays; wheeled/tracked keep phase radians */ + if (!unit_rolling(u->type) && u->anim > 0) u->anim -= dt * 3; if (u->repath_timer > 0) u->repath_timer -= dt; if (u->ability_cd > 0) u->ability_cd -= dt; @@ -576,7 +595,8 @@ void unit_update(Game *g, float dt) { u->heading = (float)atan2(wy - u->y, wx - u->x); u->x += (wx - u->x) / d * sp; u->y += (wy - u->y) / d * sp; - u->anim = 1.0f; + if (unit_rolling(u->type)) unit_roll_phase(u, sp); + else u->anim = 1.0f; } } } else if (u->state == STATE_MOVING) { @@ -585,8 +605,11 @@ void unit_update(Game *g, float dt) { if (d < 4) { u->state = STATE_IDLE; } else { float sp = u->speed * dt; + u->heading = (float)atan2(u->dest_y - u->y, u->dest_x - u->x); u->x += (u->dest_x - u->x)/d * sp; u->y += (u->dest_y - u->y)/d * sp; + if (unit_rolling(u->type)) unit_roll_phase(u, sp); + else u->anim = 1.0f; } } }