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

+115 -11
View File
@@ -1062,25 +1062,121 @@ pub const SPRITE_KINDS: [EK; 17] = [
];
// width of the sprite texture in pixels
pub const SPRITE_TEX_W: f32 = (17 * 12) as f32;
pub const SPRITE_TEX_H: f32 = 24.0;
pub const SPRITE_TEX_H: f32 = (2 * GEAR_TIERS * SPR_CELL) as f32;
pub fn sprite_index(k: EK) -> u32 {
SPRITE_KINDS.iter().position(|&s| s == k).unwrap_or(0) as u32
}
// row 0: normal sprites, row 1: white silhouettes (hit flash)
// Equipment tier of a level: 0 (Lv 1-2) none, 1 (3-4) shoulder pieces, 2 (5-6) + helmet,
// 3 (7-8) + cape, 4 (9-10) all in gold with a crown
pub const GEAR_TIERS: u32 = 5;
pub fn gear_tier(level: u32) -> u32 {
((level.max(1) - 1) / 2).min(GEAR_TIERS - 1)
}
fn is_hero_kind(k: EK) -> bool {
matches!(k, EK::Knight | EK::Archer | EK::Priest | EK::Dwarf | EK::Paladin)
}
// the 12x12 cell of a kind with its gear painted around and onto the silhouette
fn sprite_with_gear(k: EK, tier: u32) -> [[Option<Color>; 12]; 12] {
let mut g = [[None; 12]; 12];
for (y, row) in sprite_rows(k).iter().enumerate() {
for (x, ch) in row.chars().enumerate().take(SPR) {
g[y + 1][x + 1] = pal(ch);
}
}
if tier == 0 {
return g;
}
let metal = match tier {
1 => c(196, 126, 60),
4 => c(250, 206, 72),
_ => c(208, 214, 228),
};
let dark = Color::new(metal.r * 0.6, metal.g * 0.6, metal.b * 0.6, 1.0);
let span = |g: &[[Option<Color>; 12]; 12], y: usize| -> Option<(usize, usize)> {
let xs: Vec<usize> = (0..12).filter(|&x| g[y][x].is_some()).collect();
Some((*xs.first()?, *xs.last()?))
};
let top = (0..12).find(|&y| g[y].iter().any(|p| p.is_some())).unwrap_or(1);
// cape (behind the figure: only where the sprite is empty)
if tier >= 3 {
let cape = if is_hero_kind(k) { c(44, 74, 156) } else { c(128, 22, 34) };
for y in (top + 3).min(11)..=(top + 8).min(11) {
if let Some((l, r)) = span(&g, y) {
for x in [l.wrapping_sub(1), r + 1] {
if x < 12 && g[y][x].is_none() {
g[y][x] = Some(cape);
}
}
}
}
}
// shoulder pieces at the widest row of the upper body
let shoulder = (top + 2..(top + 6).min(11)).max_by_key(|&y| span(&g, y).map_or(0, |(l, r)| r - l)).unwrap_or(top + 3);
if let Some((l, r)) = span(&g, shoulder) {
for x in [l, r] {
g[shoulder][x] = Some(metal);
if shoulder + 1 < 12 && g[shoulder + 1][x].is_some() {
g[shoulder + 1][x] = Some(dark);
}
}
}
// helmet: the top row in metal, horns (creatures) or a plume (heroes)
if tier >= 2 {
if let Some((l, r)) = span(&g, top) {
for x in l..=r {
if g[top][x].is_some() {
g[top][x] = Some(metal);
}
}
if top >= 1 {
if is_hero_kind(k) {
g[top - 1][(l + r) / 2] = Some(c(200, 40, 40));
} else {
let bone = c(232, 222, 200);
if l >= 1 {
g[top - 1][l - 1] = Some(bone);
g[top][l - 1] = Some(bone);
}
if r + 1 < 12 {
g[top - 1][r + 1] = Some(bone);
g[top][r + 1] = Some(bone);
}
}
}
}
}
// crown
if tier >= 4 && top >= 1 {
if let Some((l, r)) = span(&g, top) {
let m = (l + r) / 2;
for x in [m.saturating_sub(1), m + 1] {
if x < 12 {
g[top - 1][x] = Some(c(255, 230, 90));
}
}
}
}
g
}
// rows 2*tier: sprites with the gear of the tier, rows 2*tier+1: white silhouettes (hit flash)
fn gen_sprites() -> Image {
let n = SPRITE_KINDS.len() as u32;
let mut img = Image::gen_image_color((n * SPR_CELL) as u16, (2 * SPR_CELL) as u16, Color::new(0.0, 0.0, 0.0, 0.0));
let mut img = Image::gen_image_color((n * SPR_CELL) as u16, (2 * GEAR_TIERS * SPR_CELL) as u16, Color::new(0.0, 0.0, 0.0, 0.0));
for (i, &k) in SPRITE_KINDS.iter().enumerate() {
let rows = sprite_rows(k);
for (y, row) in rows.iter().enumerate() {
for (x, ch) in row.chars().enumerate().take(SPR) {
if let Some(col) = pal(ch) {
let px = i as u32 * SPR_CELL + 1 + x as u32;
let py = 1 + y as u32;
img.set_pixel(px, py, col);
img.set_pixel(px, py + SPR_CELL, WHITE);
for tier in 0..GEAR_TIERS {
let g = sprite_with_gear(k, tier);
for (y, row) in g.iter().enumerate() {
for (x, px) in row.iter().enumerate() {
if let Some(col) = px {
let (px, py) = (i as u32 * SPR_CELL + x as u32, tier * 2 * SPR_CELL + y as u32);
img.set_pixel(px, py, *col);
img.set_pixel(px, py + SPR_CELL, WHITE);
}
}
}
}
@@ -1214,11 +1310,19 @@ impl Gfx {
Rect::new((id % ATLAS_COLS) as f32 * CELL, (id / ATLAS_COLS) as f32 * CELL, CELL, CELL)
}
// the plain 10x10 sprite (interface icons)
pub fn sprite_rect(k: EK, white: bool) -> Rect {
let i = sprite_index(k);
Rect::new((i * SPR_CELL + 1) as f32, (1 + if white { SPR_CELL } else { 0 }) as f32, SPR as f32, SPR as f32)
}
// the whole 12x12 cell with the gear of a level (billboards)
pub fn sprite_cell(k: EK, white: bool, level: u32) -> Rect {
let i = sprite_index(k);
let row = gear_tier(level) * 2 + white as u32;
Rect::new((i * SPR_CELL) as f32, (row * SPR_CELL) as f32, SPR_CELL as f32, SPR_CELL as f32)
}
pub fn tile_tex(kind: Kind, claimed: bool, hero: bool, var: u8, reinforced: bool) -> u32 {
let v = var as u32 & 3;
match kind {