Gear is part of the sprites; more detailed towers and gates

- Level gear is painted into five sprite variants per kind (shoulder pieces, helmet with horns
  or plume, cape, gold with crown), derived from each silhouette, so it moves, sways and
  flips with the unit; billboards use the 12x12 cell with the gear border
- Arrow tower: plinth, masonry courses, arrow slits, corbels, wooden platform, merlons,
  pennant and ladder; cannon tower: round body with iron bands, carriage, barrel with muzzle
  ring, cannon balls; magic tower: stepped base, buttresses, glowing rune bands
- Hero gate / gate of the order: plinths, banded pillars, capitals, stepped arch with
  keystone, half-raised portcullis, banners of the order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 13:40:56 +01:00
1 parent c9848c8948
commit 359000afa3
2 files changed
+224 -74

No files matched your search

+109 -63
View File
@@ -1020,7 +1020,6 @@ impl R3d {
}
draw_landmarks_solid(g, t);
draw_towers_solid(g);
draw_gear(g, cam);
crate::props::draw_props_solid(g, cam, t);
// ---- unlit glow / overlays
@@ -1108,7 +1107,7 @@ impl R3d {
_ => {}
}
let white = e.flash > 0.07;
let src = Gfx::sprite_rect(e.kind, white);
let src = Gfx::sprite_cell(e.kind, white, e.level);
let (mut u0, v0) = (src.x / sw, src.y / sh);
let (mut u1, v1) = ((src.x + src.w) / sw, (src.y + src.h) / sh);
if e.face < 0.0 {
@@ -1131,8 +1130,10 @@ impl R3d {
let y = dx * sr + dy * cr;
base + r * x + Vec3::Y * y
};
// the 12x12 cell (sprite with a one pixel border for the gear): the sprite's feet
// (bottom of its 11th pixel row) stay on the ground
mb.quad(
[corner(-w * 0.5, h), corner(w * 0.5, h), corner(w * 0.5, 0.0), corner(-w * 0.5, 0.0)],
[corner(-w * 0.6, h * 1.1), corner(w * 0.6, h * 1.1), corner(w * 0.6, -h * 0.1), corner(-w * 0.6, -h * 0.1)],
[vec2(u0, v0), vec2(u1, v0), vec2(u1, v1), vec2(u0, v1)],
[c; 4],
[toward; 4],
@@ -1239,53 +1240,13 @@ fn gear_shown(e: &Ent) -> bool {
e.alive && !e.held && e.captive == 0 && e.level >= 3 && !(matches!(e.job, Job::Sleep(_)) && !e.moving)
}
// Equipment that grows with the level: shoulder pieces (Lv 3+: bronze, 5+: silver, 8+: gold),
// a helmet (Lv 5+: horns for creatures, a plume for heroes) and a cape (Lv 7+).
fn draw_gear(g: &Game, cam: &Cam) {
let (_, r, _) = cam.basis();
let toward = cam.eye_dir();
for e in g.ents.iter().filter(|e| gear_shown(e)) {
let w = sprite_size(e.kind);
let mut bob = if e.moving { (e.anim * 12.0).sin().abs() * 0.06 } else { 0.0 };
if flies(e.kind) {
bob = 0.45;
}
let base = vec3(e.x, bob, e.y) + toward * 0.5;
let at = |dx: f32, dy: f32| base + r * (dx * w) + Vec3::Y * (dy * w);
let metal = if e.level >= 8 {
Color::from_rgba(240, 200, 70, 255)
} else if e.level >= 5 {
Color::from_rgba(200, 206, 220, 255)
} else {
Color::from_rgba(190, 120, 60, 255)
};
if e.level >= 7 {
// cape behind the figure
let back = base - toward * 0.1;
draw_cube(back + Vec3::Y * (0.5 * w), vec3(0.42 * w, 0.55 * w, 0.42 * w), None, if e.hero { Color::from_rgba(40, 70, 150, 255) } else { Color::from_rgba(120, 20, 30, 255) });
}
for side in [-1.0f32, 1.0] {
draw_cube(at(side * 0.26, 0.6), Vec3::splat(0.09 * w + 0.02), None, metal);
}
if e.level >= 5 {
draw_cube(at(0.0, 0.93), vec3(0.2 * w, 0.06 * w, 0.2 * w), None, metal);
if e.hero {
draw_cube(at(0.0, 1.02), vec3(0.04, 0.1 * w, 0.04), None, Color::from_rgba(200, 40, 40, 255));
} else {
for side in [-1.0f32, 1.0] {
draw_cube(at(side * 0.16, 1.0), vec3(0.035, 0.09 * w, 0.035), None, Color::from_rgba(230, 220, 200, 255));
}
}
}
}
}
// Lv 9+: a glowing ring above the head, Lv 10 also a faint aura at the feet
fn draw_gear_glow(g: &Game, cam: &Cam, t: f32) {
let toward = cam.eye_dir();
for e in g.ents.iter().filter(|e| gear_shown(e) && e.level >= 9) {
let w = sprite_size(e.kind);
let base = vec3(e.x, 0.0, e.y) + toward * 0.5;
let lift = if flies(e.kind) { 0.45 } else { 0.0 };
let base = vec3(e.x, lift, e.y) + toward * 0.5;
let c = if e.hero { Color::new(0.8, 0.9, 1.0, 0.8) } else { Color::new(1.0, 0.8, 0.3, 0.8) };
let y = w * 1.12 + (t * 2.0 + e.anim).sin() * 0.02;
for k in 0..8 {
@@ -1302,34 +1263,87 @@ fn draw_gear_glow(g: &Game, cam: &Cam, t: f32) {
fn draw_towers_solid(g: &Game) {
for tw in &g.towers {
let (x, z) = tw.pos();
let c = vec3(x, 0.0, z);
let h = tower_height(tw);
let f = 0.75 + tw.flash * 1.5; // brightens briefly when firing
// knight towers: pale stone with blue tones
let shade_c = |r: u8, g: u8, b: u8, f: f32| if tw.hero { shade_c(r / 3 + 110, g / 3 + 115, b / 3 + 135, f) } else { shade_c(r, g, b, f) };
let col = |r: u8, g: u8, b: u8| if tw.hero { shade_c(r / 3 + 110, g / 3 + 115, b / 3 + 135, f) } else { shade_c(r, g, b, f) };
let bx = |p: Vec3, s: Vec3, cl: Color| draw_cube(c + p, s, None, cl);
match tw.kind {
TowerKind::Arrow => {
draw_cube(vec3(x, h * 0.5, z), vec3(0.7, h, 0.7), None, shade_c(110, 75, 45, f));
draw_cube(vec3(x, h + 0.06, z), vec3(0.92, 0.12, 0.92), None, shade_c(130, 90, 55, f));
for (dx, dz) in [(-0.38, -0.38), (0.38, -0.38), (0.38, 0.38), (-0.38, 0.38)] {
draw_cube(vec3(x + dx, h + 0.2, z + dz), vec3(0.15, 0.18, 0.15), None, shade_c(130, 90, 55, f));
// stone plinth, masonry body with courses and arrow slits
bx(vec3(0.0, 0.08, 0.0), vec3(0.86, 0.16, 0.86), col(86, 80, 76));
bx(vec3(0.0, h * 0.5, 0.0), vec3(0.66, h, 0.66), col(118, 108, 98));
let courses = (h / 0.22) as i32;
for k in 1..courses {
bx(vec3(0.0, k as f32 * 0.22, 0.0), vec3(0.68, 0.025, 0.68), col(80, 72, 66));
}
for (dx, dz) in [(0.0f32, 0.34f32), (0.34, 0.0), (0.0, -0.34), (-0.34, 0.0)] {
bx(vec3(dx, h * 0.62, dz), vec3(if dx == 0.0 { 0.06 } else { 0.02 }, 0.2, if dz == 0.0 { 0.06 } else { 0.02 }), col(24, 18, 16));
}
// corbels and the wooden platform
for (dx, dz) in [(-0.3f32, -0.3f32), (0.3, -0.3), (0.3, 0.3), (-0.3, 0.3)] {
bx(vec3(dx * 1.2, h - 0.08, dz * 1.2), vec3(0.12, 0.12, 0.12), col(96, 88, 80));
}
bx(vec3(0.0, h + 0.04, 0.0), vec3(0.94, 0.08, 0.94), col(120, 84, 50));
// battlements: merlons with gaps
for k in 0..4 {
let d = -0.39 + k as f32 * 0.26;
for (px, pz) in [(d, -0.42), (d, 0.42), (-0.42, d), (0.42, d)] {
bx(vec3(px, h + 0.17, pz), vec3(0.11, 0.18, 0.11), col(128, 118, 106));
}
}
// pennant and ladder
bx(vec3(0.3, h + 0.45, 0.3), vec3(0.03, 0.7, 0.03), col(140, 140, 150));
bx(vec3(0.42, h + 0.7, 0.3), vec3(0.22, 0.12, 0.02), if tw.hero { shade_c(60, 90, 180, f) } else { shade_c(170, 30, 40, f) });
for k in 0..((h / 0.15) as i32) {
bx(vec3(-0.2, 0.1 + k as f32 * 0.15, 0.35), vec3(0.2, 0.025, 0.03), col(110, 76, 44));
}
bx(vec3(-0.29, h * 0.5, 0.35), vec3(0.025, h, 0.03), col(100, 70, 40));
bx(vec3(-0.11, h * 0.5, 0.35), vec3(0.025, h, 0.03), col(100, 70, 40));
}
TowerKind::Cannon => {
draw_cube(vec3(x, h * 0.5, z), vec3(0.88, h, 0.88), None, shade_c(100, 100, 110, f));
draw_cube(vec3(x, h + 0.08, z), vec3(0.5, 0.16, 0.5), None, shade_c(70, 70, 78, f));
let (ax, az) = (tw.aim.cos(), tw.aim.sin());
// squat round tower (stacked rings), iron bands, carriage, barrel, cannon balls
for k in 0..4 {
let d = 0.1 + k as f32 * 0.14;
draw_cube(vec3(x + ax * d, h + 0.2, z + az * d), Vec3::splat(0.2 - k as f32 * 0.015), None, shade_c(40, 40, 46, f));
let r = 0.92 - k as f32 * 0.03;
let y = h * (k as f32 + 0.5) / 4.0;
bx(vec3(0.0, y, 0.0), vec3(r, h / 4.0 + 0.01, r * 0.82), col(104, 104, 112));
bx(vec3(0.0, y, 0.0), vec3(r * 0.82, h / 4.0 + 0.01, r), col(96, 96, 104));
}
bx(vec3(0.0, h * 0.35, 0.0), vec3(0.94, 0.05, 0.94), col(54, 54, 60));
bx(vec3(0.0, h * 0.75, 0.0), vec3(0.9, 0.05, 0.9), col(54, 54, 60));
bx(vec3(0.0, h + 0.05, 0.0), vec3(0.62, 0.1, 0.62), col(80, 62, 44));
let (ax, az) = (tw.aim.cos(), tw.aim.sin());
// carriage cheeks beside the barrel
for side in [-1.0f32, 1.0] {
bx(vec3(-az * 0.14 * side, h + 0.17, ax * 0.14 * side), vec3(0.14, 0.16, 0.14), col(96, 70, 44));
}
for k in 0..5 {
let d = 0.0 + k as f32 * 0.13;
let r = 0.2 - k as f32 * 0.012;
bx(vec3(ax * d, h + 0.22, az * d), Vec3::splat(r), col(40, 40, 46));
}
bx(vec3(ax * 0.58, h + 0.22, az * 0.58), Vec3::splat(0.2), col(70, 70, 78));
for (dx, dz, y) in [(0.3f32, -0.28f32, 0.0f32), (0.38, -0.2, 0.0), (0.34, -0.24, 0.08)] {
bx(vec3(dx, h + 0.15 + y, dz), Vec3::splat(0.07), col(30, 30, 34));
}
}
TowerKind::Magic => {
draw_cube(vec3(x, h * 0.5, z), vec3(0.42, h, 0.42), None, shade_c(84, 74, 104, f));
draw_cube(vec3(x, 0.1, z), vec3(0.75, 0.2, 0.75), None, shade_c(70, 62, 86, f));
draw_cube(vec3(x, h, z), vec3(0.6, 0.1, 0.6), None, shade_c(110, 96, 130, f));
// rune-cut obelisk on a stepped base with buttresses (crystal and runes glow)
bx(vec3(0.0, 0.06, 0.0), vec3(0.82, 0.12, 0.82), col(62, 56, 76));
bx(vec3(0.0, 0.17, 0.0), vec3(0.62, 0.1, 0.62), col(72, 64, 90));
bx(vec3(0.0, h * 0.5, 0.0), vec3(0.4, h, 0.4), col(84, 74, 104));
bx(vec3(0.0, h * 0.75, 0.0), vec3(0.32, h * 0.5, 0.46), col(92, 82, 114));
for (dx, dz) in [(0.25f32, 0.0f32), (-0.25, 0.0), (0.0, 0.25), (0.0, -0.25)] {
bx(vec3(dx, h * 0.22, dz), vec3(if dx == 0.0 { 0.12 } else { 0.1 }, h * 0.44, if dz == 0.0 { 0.12 } else { 0.1 }), col(70, 62, 88));
}
bx(vec3(0.0, h, 0.0), vec3(0.58, 0.08, 0.58), col(110, 96, 130));
for (dx, dz) in [(-0.24f32, -0.24f32), (0.24, -0.24), (0.24, 0.24), (-0.24, 0.24)] {
bx(vec3(dx, h + 0.12, dz), vec3(0.06, 0.18, 0.06), col(120, 104, 140));
}
}
}
// level pips
// level pips on the plinth
for k in 0..tw.level {
draw_cube(vec3(x - 0.2 + k as f32 * 0.2, 0.25, z + 0.46), Vec3::splat(0.09), None, Color::from_rgba(240, 200, 70, 255));
}
@@ -1346,6 +1360,15 @@ fn draw_towers_glow(g: &Game, t: f32) {
let pulse = 1.0 + (t * 4.0 + k as f32).sin() * 0.1 + tw.flash * 2.0;
draw_sphere(c, 0.16 * pulse, None, Color::new(0.85, 0.5, 1.0, 0.95));
draw_sphere(c, 0.3 * pulse, None, Color::new(0.7, 0.3, 1.0, 0.25));
// runes on the four faces of the obelisk, glowing one after another
let h = tower_height(tw);
for r in 0..3 {
let y = h * (0.35 + r as f32 * 0.22);
let glow = 0.4 + 0.6 * ((t * 2.5 - r as f32 * 0.8 + k as f32).sin() * 0.5 + 0.5);
for (dx, dz) in [(0.21f32, 0.0f32), (-0.21, 0.0), (0.0, 0.21), (0.0, -0.21)] {
draw_cube(vec3(x + dx, y, z + dz), vec3(if dx == 0.0 { 0.1 } else { 0.02 }, 0.08, if dz == 0.0 { 0.1 } else { 0.02 }), None, Color::new(0.8, 0.45, 1.0, glow));
}
}
}
}
@@ -1419,10 +1442,33 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
let (gx, gz) = g.gate_center();
if g.gate_hp > 0.0 {
let stone = Color::from_rgba(150, 155, 172, 255);
draw_cube(vec3(gx - 1.1, 1.0, gz), vec3(0.45, 2.0, 0.45), None, stone);
draw_cube(vec3(gx + 1.1, 1.0, gz), vec3(0.45, 2.0, 0.45), None, stone);
draw_cube(vec3(gx, 2.1, gz), vec3(2.8, 0.35, 0.55), None, stone);
draw_cube(vec3(gx, 2.35, gz), vec3(0.5, 0.2, 0.5), None, Color::from_rgba(230, 200, 100, 255));
let dark = Color::from_rgba(112, 116, 132, 255);
let gold = Color::from_rgba(230, 200, 100, 255);
let iron = Color::from_rgba(56, 58, 66, 255);
for side in [-1.1f32, 1.1] {
draw_cube(vec3(gx + side, 0.12, gz), vec3(0.6, 0.24, 0.6), None, dark); // plinth
draw_cube(vec3(gx + side, 1.0, gz), vec3(0.45, 1.8, 0.45), None, stone);
for y in [0.6f32, 1.2] {
draw_cube(vec3(gx + side, y, gz), vec3(0.48, 0.05, 0.48), None, dark); // bands
}
draw_cube(vec3(gx + side, 1.92, gz), vec3(0.56, 0.12, 0.56), None, dark); // capital
// banner of the order: white with a golden sun
draw_cube(vec3(gx + side, 1.35, gz + 0.25), vec3(0.32, 0.7, 0.02), None, Color::from_rgba(232, 234, 242, 255));
draw_cube(vec3(gx + side, 1.45, gz + 0.265), vec3(0.14, 0.14, 0.01), None, gold);
draw_cube(vec3(gx + side, 1.02, gz + 0.26), vec3(0.32, 0.05, 0.02), None, Color::from_rgba(60, 90, 170, 255));
}
// stepped arch with a keystone
draw_cube(vec3(gx, 2.1, gz), vec3(2.8, 0.3, 0.55), None, stone);
draw_cube(vec3(gx, 1.88, gz), vec3(1.7, 0.14, 0.5), None, dark);
draw_cube(vec3(gx, 2.32, gz), vec3(2.2, 0.14, 0.5), None, dark);
draw_cube(vec3(gx, 2.2, gz), vec3(0.34, 0.5, 0.58), None, gold);
// portcullis (iron bars) in the opening, half raised
for k in 0..5 {
draw_cube(vec3(gx - 0.6 + k as f32 * 0.3, 1.45, gz), vec3(0.05, 0.85, 0.05), None, iron);
}
for y in [1.15f32, 1.5, 1.8] {
draw_cube(vec3(gx, y, gz), vec3(1.7, 0.05, 0.05), None, iron);
}
} else {
for k in 0..10 {
let a = k as f32 * 1.7;