Rivers and bridges: water and lava tiles, bridge tool, burning in lava
- New tile kinds (appended for the savegame codec): water, lava, wooden bridge, stone bridge, core shard - Sunken river beds with banks, bridge decks with rails, lava glow and lights - Bridge tool (key U, rooms category): bridges grow from own ground, 30 gold/tile over water, 75 over lava - Selling a bridge drops the units on it into the river (water: swim to the bank, lava: burn) - Cannon blasts and punches knock units back; into lava = burnt (heroes still pay their bounty) - Heroes cannot path through rivers; texts EN/DE; tests for bridges and lava Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
af6be62805
commit
75dc445d3b
9 files changed
+536
-22
No files matched your search
+94
-1
@@ -14,7 +14,7 @@ pub const WALL_H: f32 = 0.95;
|
||||
const CHUNK: i32 = 16;
|
||||
pub const MAX_LIGHTS: usize = 24;
|
||||
const ATLAS_W: f32 = 256.0;
|
||||
const ATLAS_H: f32 = 96.0;
|
||||
const ATLAS_H: f32 = 128.0;
|
||||
const DISP_AMP: f32 = 0.11; // wall surface irregularity
|
||||
|
||||
// ------------------------------------------------------------------ camera
|
||||
@@ -358,6 +358,9 @@ fn ambient_field(map: &Map) -> Vec<f32> {
|
||||
(false, Owner::Hero) => 0.6,
|
||||
_ => 0.0,
|
||||
};
|
||||
if t.river() == Some(Kind::Lava) {
|
||||
f[i] = f[i].max(0.8); // lava lights up its surroundings
|
||||
}
|
||||
if t.kind == Kind::Core {
|
||||
f[i] = 1.0;
|
||||
}
|
||||
@@ -401,6 +404,8 @@ fn solid_at(map: &Map, x: i32, z: i32) -> bool {
|
||||
}
|
||||
|
||||
const OUTLINE_W: f32 = 0.035;
|
||||
const LIQ_D: f32 = 0.3; // depth of the river surface below the floor
|
||||
const DECK_Y: f32 = 0.03; // height of bridge decks
|
||||
const OUTLINE_C: [u8; 4] = [8, 5, 6, 150];
|
||||
|
||||
// Displaced points along a straight edge (same displacement as the wall grids -> lines follow the surface)
|
||||
@@ -487,6 +492,75 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
|
||||
let tex = Gfx::tile_tex(t.kind, t.owner == Owner::Player, t.owner == Owner::Hero, t.var, t.reinforced);
|
||||
let (ua, ub) = cell_uv(tex);
|
||||
let (xf, zf) = (x as f32, z as f32);
|
||||
if let Some(river) = t.river() {
|
||||
// ---- river bed: sunken surface, banks where the ground or a wall begins
|
||||
let lava = river == Kind::Lava;
|
||||
let (la, lb) = cell_uv(if lava { T_LAVA } else { T_WATER } + (t.var as u32 & 3));
|
||||
let a = amb_at(amb, xf + 0.5, zf + 0.5);
|
||||
let lc = if lava { col(vec3(1.0, 0.95, 0.9), 1.0) } else { col(Vec3::splat(0.25 + a * 0.9), 1.0) };
|
||||
mb.quad(
|
||||
[vec3(xf, -LIQ_D, zf), vec3(xf + 1.0, -LIQ_D, zf), vec3(xf + 1.0, -LIQ_D, zf + 1.0), vec3(xf, -LIQ_D, zf + 1.0)],
|
||||
[vec2(la.x, la.y), vec2(lb.x, la.y), vec2(lb.x, lb.y), vec2(la.x, lb.y)],
|
||||
[lc; 4],
|
||||
[Vec3::Y; 4],
|
||||
);
|
||||
let (fa, fb) = cell_uv(T_FACE_EARTH);
|
||||
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
|
||||
if map.get(x + dx, z + dz).map_or(false, |o| o.river().is_some()) {
|
||||
continue;
|
||||
}
|
||||
// edge seen from inside the river: from a to b, facing back into this tile
|
||||
let (p, q) = match d {
|
||||
0 => (vec3(xf, 0.0, zf), vec3(xf + 1.0, 0.0, zf)),
|
||||
1 => (vec3(xf + 1.0, 0.0, zf), vec3(xf + 1.0, 0.0, zf + 1.0)),
|
||||
2 => (vec3(xf + 1.0, 0.0, zf + 1.0), vec3(xf, 0.0, zf + 1.0)),
|
||||
_ => (vec3(xf, 0.0, zf + 1.0), vec3(xf, 0.0, zf)),
|
||||
};
|
||||
let inward = vec3(-dx as f32, 0.0, -dz as f32);
|
||||
let bc = col(Vec3::splat(if lava { 0.7 } else { 0.18 + a * 0.5 }), 1.0);
|
||||
mb.quad(
|
||||
[p, q, q - Vec3::Y * LIQ_D, p - Vec3::Y * LIQ_D],
|
||||
[vec2(fa.x, fa.y), vec2(fb.x, fa.y), vec2(fb.x, fb.y), vec2(fa.x, fb.y)],
|
||||
[bc; 4],
|
||||
[inward; 4],
|
||||
);
|
||||
}
|
||||
if t.bridge() {
|
||||
// deck: planks across the direction of the bridge, rails along it
|
||||
let run_x = |dx: i32| map.get(x + dx, z).map_or(false, |o| o.bridge() || (o.walkable() && !o.bridge()));
|
||||
let run_z = |dz: i32| map.get(x, z + dz).map_or(false, |o| o.bridge() || (o.walkable() && !o.bridge()));
|
||||
let along_x = (run_x(-1) as i32 + run_x(1) as i32) > (run_z(-1) as i32 + run_z(1) as i32);
|
||||
let (ba, bb) = cell_uv(tex);
|
||||
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5).max(if lava { 0.6 } else { 0.0 })), 1.0);
|
||||
let uv = if along_x {
|
||||
[vec2(ba.x, ba.y), vec2(ba.x, bb.y), vec2(bb.x, bb.y), vec2(bb.x, ba.y)]
|
||||
} else {
|
||||
[vec2(ba.x, ba.y), vec2(bb.x, ba.y), vec2(bb.x, bb.y), vec2(ba.x, bb.y)]
|
||||
};
|
||||
mb.quad([vec3(xf, DECK_Y, zf), vec3(xf + 1.0, DECK_Y, zf), vec3(xf + 1.0, DECK_Y, zf + 1.0), vec3(xf, DECK_Y, zf + 1.0)], uv, [dc; 4], [Vec3::Y; 4]);
|
||||
// deck edges above open river
|
||||
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
|
||||
if !map.get(x + dx, z + dz).map_or(false, |o| o.liquid()) {
|
||||
continue;
|
||||
}
|
||||
let (p, q) = match d {
|
||||
0 => (vec3(xf + 1.0, DECK_Y, zf), vec3(xf, DECK_Y, zf)),
|
||||
1 => (vec3(xf + 1.0, DECK_Y, zf + 1.0), vec3(xf + 1.0, DECK_Y, zf)),
|
||||
2 => (vec3(xf, DECK_Y, zf + 1.0), vec3(xf + 1.0, DECK_Y, zf + 1.0)),
|
||||
_ => (vec3(xf, DECK_Y, zf), vec3(xf, DECK_Y, zf + 1.0)),
|
||||
};
|
||||
let out = vec3(dx as f32, 0.0, dz as f32);
|
||||
let ec = col(Vec3::splat(0.3), 1.0);
|
||||
mb.quad([p, q, q - Vec3::Y * 0.14, p - Vec3::Y * 0.14], [vec2(ba.x, ba.y), vec2(bb.x, ba.y), vec2(bb.x, ba.y + 0.002), vec2(ba.x, ba.y + 0.002)], [ec; 4], [out; 4]);
|
||||
ln.strip(p + out * 0.01, q + out * 0.01, Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if t.kind == Kind::Shard {
|
||||
let dc = col(Vec3::splat(1.0), 1.0);
|
||||
mb.cross(xf + 0.5, zf + 0.5, 0.7, 0.8, D_SHARD, dc);
|
||||
}
|
||||
if !t.solid() {
|
||||
// floor with corner ambient occlusion
|
||||
let corners = [(0, 0), (1, 0), (1, 1), (0, 1)];
|
||||
@@ -807,6 +881,20 @@ impl R3d {
|
||||
let (gx, gz) = g.gate_center();
|
||||
push(vec3(gx, 1.2, gz), 7.0, vec3(0.75, 0.85, 1.0), 1.0, 50.0);
|
||||
}
|
||||
// glowing lava near the camera (every third tile)
|
||||
let r = (cam.zoom * 0.9) as i32;
|
||||
let (ccx, ccz) = (cam.tx as i32, cam.tz as i32);
|
||||
for z in (ccz - r).max(0)..(ccz + r).min(mh() as i32) {
|
||||
if z % 3 != 0 {
|
||||
continue;
|
||||
}
|
||||
for x in (ccx - r).max(0)..(ccx + r).min(mw() as i32) {
|
||||
if x % 3 == 0 && g.map.t[idx(x, z)].kind == Kind::Lava {
|
||||
let fl = 0.8 + 0.2 * (t * 2.0 + (x * 7 + z * 3) as f32).sin();
|
||||
push(vec3(x as f32 + 0.5, 0.3, z as f32 + 0.5), 4.0, vec3(1.0, 0.45, 0.12), 0.9 * fl, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
let pulse = 0.5 + 0.5 * (t * 4.0).sin();
|
||||
for &(ex, ey) in &g.planned_entries {
|
||||
push(vec3(ex as f32 + 0.5, 1.2, ey as f32 + 0.5), 5.0, vec3(1.0, 0.12, 0.08), 0.6 + 1.0 * pulse, 30.0);
|
||||
@@ -1453,5 +1541,10 @@ pub fn minimap_color(tile: &Tile) -> [u8; 4] {
|
||||
Kind::Torture => [120, 40, 40, 255],
|
||||
Kind::Tower => [255, 140, 60, 255],
|
||||
Kind::Temple => [200, 110, 50, 255],
|
||||
Kind::Water => [40, 80, 150, 255],
|
||||
Kind::Lava => [250, 110, 30, 255],
|
||||
Kind::Bridge => [150, 110, 60, 255],
|
||||
Kind::LavaBridge => [130, 120, 115, 255],
|
||||
Kind::Shard => [255, 60, 120, 255],
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user