Dwarf caves in all modes: neutral dwarves guard a hoard and sometimes a rune chest; the rune forge tower; imps repair towers before claiming

- 1-4 cave slots by map size, 50% each (at least one); knight treasure caves only where a mission places them
- dwarves fight intruders near the cave, never chase far, heal at home, grow with distance and the player's army, never count for victory
- rune chest: one free rune forge tower (chain bolt to three more enemies), upgradeable for gold, sold once it comes back (next mission / free play at once), unbuilt ones carry over
- new tool (key N) shown in the tower menu while a rune tower can be built or upgraded

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 15:14:36 +01:00
1 parent d92b45a0b8
commit 9a9f04ae1f
17 files changed
+658 -32

No files matched your search

+54
View File
@@ -1347,6 +1347,34 @@ fn draw_towers_solid(g: &Game) {
bx(vec3(dx, h + 0.12, dz), vec3(0.06, 0.18, 0.06), col(120, 104, 140));
}
}
TowerKind::Rune => {
// dwarven rune forge: broad granite foot, bronze bands, a tapering keep with
// carved buttresses, an anvil crown and a forge hearth on top (runes glow)
bx(vec3(0.0, 0.07, 0.0), vec3(0.96, 0.14, 0.96), col(70, 66, 70));
bx(vec3(0.0, 0.19, 0.0), vec3(0.84, 0.1, 0.84), col(84, 80, 86));
let steps = 5;
for k in 0..steps {
let y0 = 0.24 + (h - 0.24) * k as f32 / steps as f32;
let hh = (h - 0.24) / steps as f32;
let w = 0.72 - k as f32 * 0.05;
bx(vec3(0.0, y0 + hh * 0.5, 0.0), vec3(w, hh + 0.005, w), col(96 - k as u8 * 2, 92 - k as u8 * 2, 98));
bx(vec3(0.0, y0 + 0.01, 0.0), vec3(w + 0.03, 0.035, w + 0.03), shade_c(176, 112, 56, f));
}
for (dx, dz) in [(0.38f32, 0.38f32), (-0.38, 0.38), (0.38, -0.38), (-0.38, -0.38)] {
bx(vec3(dx, h * 0.3, dz), vec3(0.14, h * 0.6, 0.14), col(78, 74, 80));
bx(vec3(dx, h * 0.6 + 0.05, dz), vec3(0.17, 0.08, 0.17), shade_c(176, 112, 56, f));
}
// gate with a bronze door
bx(vec3(0.0, 0.42, 0.37), vec3(0.26, 0.4, 0.04), shade_c(120, 76, 40, f));
bx(vec3(0.0, 0.64, 0.38), vec3(0.32, 0.06, 0.05), col(110, 106, 112));
// anvil crown and hearth
bx(vec3(0.0, h + 0.05, 0.0), vec3(0.62, 0.1, 0.62), col(60, 58, 64));
bx(vec3(0.0, h + 0.16, 0.0), vec3(0.3, 0.12, 0.42), col(48, 48, 54));
bx(vec3(0.0, h + 0.25, 0.0), vec3(0.42, 0.08, 0.56), col(56, 56, 62));
for (dx, dz) in [(-0.27f32, -0.27f32), (0.27, -0.27), (0.27, 0.27), (-0.27, 0.27)] {
bx(vec3(dx, h + 0.16, dz), vec3(0.08, 0.22, 0.08), shade_c(176, 112, 56, f));
}
}
}
// level pips on the plinth
for k in 0..tw.level {
@@ -1357,6 +1385,29 @@ fn draw_towers_solid(g: &Game) {
fn draw_towers_glow(g: &Game, t: f32) {
for (k, tw) in g.towers.iter().enumerate() {
if tw.kind == TowerKind::Rune {
// forge glow on the crown, runes on the keep and three rune stones circling it
let (x, z) = tw.pos();
let h = tower_height(tw);
let pulse = 1.0 + (t * 3.0 + k as f32).sin() * 0.12 + tw.flash * 2.5;
draw_sphere(vec3(x, h + 0.34, z), 0.1 * pulse, None, Color::new(1.0, 0.75, 0.3, 0.95));
draw_sphere(vec3(x, h + 0.36, z), 0.24 * pulse, None, Color::new(1.0, 0.55, 0.15, 0.22));
for r in 0..4 {
let y = 0.4 + (h - 0.5) * r as f32 / 4.0;
let glow = 0.35 + 0.65 * ((t * 2.0 - r as f32 * 0.9 + k as f32).sin() * 0.5 + 0.5);
let w = 0.72 - r as f32 * 0.05;
for (dx, dz) in [(w * 0.5 + 0.01, 0.0f32), (-w * 0.5 - 0.01, 0.0), (0.0, w * 0.5 + 0.01), (0.0, -w * 0.5 - 0.01)] {
draw_cube(vec3(x + dx, y + 0.1, z + dz), vec3(if dx == 0.0 { 0.12 } else { 0.02 }, 0.1, if dz == 0.0 { 0.12 } else { 0.02 }), None, Color::new(0.35, 0.8, 1.0, glow));
}
}
for n in 0..(2 + tw.level) {
let a = t * 1.2 + n as f32 * std::f32::consts::TAU / (2 + tw.level) as f32;
let p = vec3(x + a.cos() * 0.62, h * 0.7 + (t * 2.0 + n as f32).sin() * 0.08, z + a.sin() * 0.62);
draw_cube(p, vec3(0.1, 0.12, 0.1), None, Color::new(0.3, 0.75, 1.0, 0.9));
draw_sphere(p, 0.12, None, Color::new(0.3, 0.7, 1.0, 0.2));
}
continue;
}
if tw.kind != TowerKind::Magic {
continue;
}
@@ -1595,6 +1646,8 @@ fn draw_shadows(g: &Game) {
disc(vec3(e.x, 0.012, e.y), r, Color::new(0.0, 0.0, 0.0, 0.4), 12);
let rc = if e.captive != 0 {
Color::new(0.6, 0.6, 0.6, 0.6)
} else if e.dwarf() {
Color::new(0.85, 0.55, 0.25, 0.85)
} else if e.rival() {
if e.side == 2 {
Color::new(0.35, 0.95, 0.4, 0.85)
@@ -1806,6 +1859,7 @@ pub fn minimap_color(tile: &Tile) -> [u8; 4] {
Kind::Aerie => [170, 140, 90, 255],
Kind::Steel => [150, 160, 180, 255],
Kind::ManaSpring => [90, 230, 255, 255],
Kind::RuneChest => [80, 200, 255, 255],
}
}