High-res textures for all rooms, special floors, walls, bridges, outdoors and objects; reinforced wall as castle masonry in both styles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-07 22:25:50 +01:00
1 parent da40b0875e
commit 2e905fe374
279 files changed
+307 -35

No files matched your search

+123 -33
View File
@@ -357,11 +357,66 @@ const HD_TILES: [(u32, u32, &[u8], &[u8]); 2] = [
// 128x128 ones, weights)
pub const T_HDSET: u32 = 224;
// the floor decals (wear and tear laid over plain floors) in the high-res style: transparent
// pictures for their cells (cell, 64x64, 128x128)
const HD_DECALS: [(u32, &[u8], &[u8]); 2] = [
// the decals (wear and tear laid over plain floors) and the upright objects (treasury heaps,
// mushrooms, the core shard) in the high-res style: transparent pictures for their cells
// (cell, 64x64, 128x128)
macro_rules! hd_obj {
($id:expr, $name:literal) => {
($id, include_bytes!(concat!("../assets/textures/hd/obj-", $name, "-64.png")), include_bytes!(concat!("../assets/textures/hd/obj-", $name, "-128.png")))
};
}
const HD_DECALS: [(u32, &[u8], &[u8]); 8] = [
(D_RUBBLE, include_bytes!("../assets/textures/decal-rubble.png"), include_bytes!("../assets/textures/decal-rubble-128.png")),
(D_CRACK, include_bytes!("../assets/textures/decal-crack.png"), include_bytes!("../assets/textures/decal-crack-128.png")),
hd_obj!(D_COINS, "coins"),
hd_obj!(D_SILVER, "silver"),
hd_obj!(D_RUBY, "ruby"),
hd_obj!(D_DIAMOND, "diamond"),
hd_obj!(D_MUSHROOM, "mushroom"),
hd_obj!(D_SHARD, "shard"),
];
// Everything else in the world (see art/generated/textures/all/): a kind's own cells get as many
// variants as it has cells, tiles that fit together in any order (one seamless border, different
// middles), and a tile picks one by its position. (first cell, cells, 64x64 tiles side by side,
// 128x128 ones)
macro_rules! hd_var {
($id:expr, $n:expr, $name:literal) => {
($id, $n, include_bytes!(concat!("../assets/textures/hd/", $name, "-v64.png")), include_bytes!(concat!("../assets/textures/hd/", $name, "-v128.png")))
};
}
const HD_VARS: [(u32, u32, &[u8], &[u8]); 31] = [
hd_var!(T_FORTWALL, 4, "fortwall"),
hd_var!(T_STEEL, 4, "steel"),
hd_var!(T_OBSIDIAN, 4, "obsidian"),
hd_var!(T_FACE_EARTH, 1, "face"),
hd_var!(T_TREASURY, 4, "treasury"),
hd_var!(T_LAIR, 4, "lair"),
hd_var!(T_FARM, 4, "farm"),
hd_var!(T_TRAINING, 4, "training"),
hd_var!(T_LIBRARY, 4, "library"),
hd_var!(T_TORTURE, 4, "torture"),
hd_var!(T_TEMPLE, 4, "temple"),
hd_var!(T_PRISON, 4, "prison"),
hd_var!(T_GRAVEYARD, 4, "graveyard"),
hd_var!(T_GUARDPOST, 4, "guardpost"),
hd_var!(T_ARENA, 4, "arena"),
hd_var!(T_ALTAR, 4, "altar"),
hd_var!(T_FORGE, 4, "forge"),
hd_var!(T_AERIE, 4, "aerie"),
hd_var!(T_COREFLOOR, 4, "core"),
hd_var!(T_PORTALFLOOR, 4, "portal"),
hd_var!(T_GATEFLOOR, 4, "gate"),
hd_var!(T_HEROFLOOR, 4, "hero"),
hd_var!(T_BRIDGE, 2, "bridge"),
hd_var!(T_STONEBRIDGE, 2, "stonebridge"),
hd_var!(T_GRASS, 4, "grass"),
hd_var!(T_SNOW, 4, "snow"),
hd_var!(T_MOSS, 4, "moss"),
hd_var!(T_LEAVES, 2, "leaves"),
hd_var!(T_BARK, 1, "bark"),
hd_var!(T_STEM, 1, "stem"),
hd_var!(T_CAP, 1, "cap"),
];
const HD_SETS: [(u32, u32, u32, &[u8], &[u8], [u32; 5]); 12] = [
(T_CLAIM, 4, T_HDSET, include_bytes!("../assets/textures/floor-player.png"), include_bytes!("../assets/textures/floor-player-128.png"), [1; 5]),
@@ -392,6 +447,11 @@ pub fn hd_tile(id: u32, x: i32, z: i32) -> u32 {
r -= wk;
}
}
if let Some(&(base, n, _, _)) = HD_VARS.iter().find(|&&(base, n, _, _)| (base..base + n).contains(&id)) {
let h = (x as u32).wrapping_mul(2_654_435_761) ^ (z as u32).wrapping_mul(40_503);
let h = (h ^ (h >> 15)).wrapping_mul(0x2c1b_3c6d);
return base + (h >> 9) % n;
}
match HD_TILES.iter().find(|&&(base, n, _, _)| (base..base + n).contains(&id)) {
Some(&(base, 4, _, _)) => base + (x & 1) as u32 + (z & 1) as u32 * 2,
_ => id,
@@ -434,6 +494,18 @@ fn gen_hd_atlas(tile: u32) -> Image {
}
}
}
for &(base, n, png, png2) in HD_VARS.iter() {
let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).expect("hd variants");
for v in 0..n {
let id = base + v;
let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
for y in 0..tile {
for x in 0..tile {
img.set_pixel(ox + x, oy + y, src.get_pixel(v * tile + x, y));
}
}
}
}
for &(id, png, png2) in HD_DECALS.iter() {
let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).expect("hd decal");
let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile);
@@ -653,41 +725,47 @@ fn gen_atlas() -> Image {
});
// reinforced walls: masonry in dark stone with the player's red mortar
for v in 0..4u32 {
// reinforced wall: roughly hewn grey stones of different sizes (a jittered grid of
// stone centres, their borders the mortar; wrapped, so the cell is seamless), dark mortar
// with a crimson tint, the upper rim of every stone lit, the lower rim in shadow
// reinforced wall: a castle wall of roughly squared grey blocks in regular courses (three
// courses of 6, 5 and 5 pixels, every course cut into blocks of 5-9 pixels at its own places;
// wrapped, so the cell is seamless), dark mortar with a crimson tint, the top of every
// block lit, its bottom in shadow, now and then a chipped corner
cv.cell(T_REINF + v, |x, y| {
// (distance to the nearest centre, to the second nearest, the nearest stone, its centre's y)
let mut best = (99.0f32, 99.0f32, 0i32, 0.0f32);
for gy in 0..3 {
for gx in 0..3 {
let cx = gx as f32 * 5.33 + 1.0 + (hash(gx, gy, v + 95) % 4) as f32;
let cy = gy as f32 * 5.33 + 1.0 + (hash(gy, gx, v + 96) % 4) as f32;
for wx in [-16.0f32, 0.0, 16.0] {
for wy in [-16.0f32, 0.0, 16.0] {
// stones a little wider than high, like coursed rubble
let (dx, dy) = ((x as f32 + 0.5 - cx - wx) * 0.8, y as f32 + 0.5 - cy - wy);
let d = (dx * dx + dy * dy).sqrt();
if d < best.0 {
best = (d, best.0, gy * 3 + gx, cy + wy);
} else if d < best.1 {
best.1 = d;
}
}
}
let (row, ry, rh) = match y {
0..=5 => (0, y, 6),
6..=10 => (1, y - 6, 5),
_ => (2, y - 11, 5),
};
// the course's cuts: from an offset on, blocks of 5-9 pixels around the cell
let off = (hash(row, 1, v + 95) % 6) as i32;
let mut cuts = vec![off];
let mut c = off;
let mut k = 0;
loop {
c += 5 + (hash(row, k, v + 96) % 5) as i32;
k += 1;
if c > off + 16 - 4 {
break;
}
cuts.push(c);
}
let (d1, d2, id, cy) = best;
if d2 - d1 < 0.9 {
return Some(shade((44, 30, 34), (rnd(x, y, v + 97) * 8.0) as i32));
let xs = (x - off).rem_euclid(16) + off;
let block = cuts.iter().filter(|&&q| q <= xs).count() as i32;
let start = cuts[(block - 1).max(0) as usize];
let end = cuts.get(block as usize).copied().unwrap_or(off + 16);
let mortar = ry == rh - 1 || xs == end - 1;
let chip = (xs == start || xs == end - 2) && (ry == 0 || ry == rh - 2) && hash(row * 7 + block, ry, v + 97) % 4 == 0;
if mortar || chip {
return Some(shade((46, 30, 34), (rnd(x, y, v + 98) * 8.0) as i32));
}
let tone = (hash(id, 7, v + 98) % 5) as i32 * 5 - 10;
let rim = if d2 - d1 < 2.0 {
if (y as f32 + 0.5) < cy { 14 } else { -14 }
let tone = (hash(row * 11 + block, 3, v + 99) % 5) as i32 * 5 - 10;
let face = if ry == 0 {
12
} else if ry == rh - 2 {
-8
} else {
0
};
Some(shade((92, 90, 96), tone + rim + (rnd(x, y, v + 99) * 10.0) as i32 - 5))
Some(shade((92, 90, 96), tone + face + (rnd(x, y, v + 100) * 10.0) as i32 - 5))
});
// torture chamber: dark stones with rusty iron grates
cv.cell(T_TORTURE + v, |x, y| {
@@ -2800,7 +2878,7 @@ mod hd_atlas_tests {
(p.r, p.g, p.b, p.a)
};
// a cell without an own texture: the 16x16 one scaled up
let (cx, cy) = ((T_TREASURY % ATLAS_COLS) * 16, (T_TREASURY / ATLAS_COLS) * 16);
let (cx, cy) = ((T_PUMPKIN % ATLAS_COLS) * 16, (T_PUMPKIN / ATLAS_COLS) * 16);
for (x, y) in [(0, 0), (5, 9), (15, 15)] {
assert_eq!(px(&hd, (cx + x) * k + 2, (cy + y) * k + 1), px(&small, cx + x, cy + y));
}
@@ -2828,6 +2906,18 @@ mod hd_atlas_tests {
}
}
}
// the variants: as many tiles as the kind has cells, every cell filled
for &(base, n, png, png2) in HD_VARS.iter() {
for tile in [HD_TILE, HD_TILE2] {
let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).unwrap();
assert_eq!((src.width as u32, src.height as u32), (n * tile, tile), "cell {base}");
}
for z in 0..8 {
for x in 0..8 {
assert!((base..base + n).contains(&hd_tile(base, x, z)));
}
}
}
// the decals: transparent around their pictures
for &(id, _, _) in HD_DECALS.iter() {
for tile in [HD_TILE, HD_TILE2] {
@@ -2854,7 +2944,7 @@ mod hd_atlas_tests {
// the liquids: four variants by position; others stay
assert_eq!(hd_tile(T_WATER + 3, 0, 0), T_WATER);
assert_eq!(hd_tile(T_LAVA, 1, 1), T_LAVA + 3);
assert_eq!(hd_tile(T_TREASURY + 2, 1, 0), T_TREASURY + 2);
assert_eq!(hd_tile(T_PUMPKIN + 2, 1, 0), T_PUMPKIN + 2);
}
}