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

+9 -2
View File
@@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
// From which mission (0-based) on each tool is available (same order as ui::TOOLS):
// dig, hand, treasury, lair, farm, training, library, sell, flag, lightning, heal, imp,
// torture, temple, arrow tower, cannon tower, magic tower, bridge, aerie, steel
pub const TOOL_UNLOCK: [usize; 20] = [0, 0, 0, 0, 0, 2, 5, 0, 0, 3, 5, 0, 8, 12, 3, 7, 12, 1, 8, 17];
pub const TOOL_UNLOCK: [usize; 21] = [0, 0, 0, 0, 0, 2, 5, 0, 0, 3, 5, 0, 8, 12, 3, 7, 12, 1, 8, 17, 0];
pub struct FortDef {
pub at: (f32, f32), // centre as a fraction of the map size
@@ -79,6 +79,10 @@ fn cave(at: (f32, f32), chained: Option<(EK, u32)>, treasure: i32) -> CaveDef {
pub struct Carry {
pub creatures: Vec<(EK, u32, f32)>, // kind, level, experience
pub gold: f32,
#[serde(default)]
pub rune_free: u32, // unbuilt rune forge towers from chests
#[serde(default)]
pub rune_final: u32, // unbuilt or sold rune forge towers that were already given back once
}
pub const CARRY_CREATURES: usize = 5;
@@ -499,6 +503,7 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
let y = ((ry * h as f32) as i32).clamp(10, h as i32 - 10);
g.add_rival(side, x, y, diff);
}
g.place_dwarf_caves(d.seed);
let heart = d.gate || d.forts.iter().any(|f| f.heart);
if !heart {
g.gate_hp = 0.0;
@@ -524,6 +529,8 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
if let Some(c) = carry {
joiners.extend(c.creatures.iter().copied());
g.gold += c.gold;
g.rune_free += c.rune_free;
g.rune_final += c.rune_final;
}
for (k, &(kind, level, xp)) in joiners.iter().enumerate() {
let a = k as f32 * 0.9;
@@ -543,7 +550,7 @@ pub fn carry_from(g: &Game) -> Carry {
let mut c: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect();
let power = |e: &Ent| e.max_hp * e.atk() / st(e.kind).interval;
c.sort_by(|a, b| power(b).partial_cmp(&power(a)).unwrap_or(std::cmp::Ordering::Equal));
Carry { creatures: c.iter().take(CARRY_CREATURES).map(|e| (e.kind, e.level, e.xp)).collect(), gold: (g.gold * CARRY_GOLD).floor() }
Carry { creatures: c.iter().take(CARRY_CREATURES).map(|e| (e.kind, e.level, e.xp)).collect(), gold: (g.gold * CARRY_GOLD).floor(), rune_free: g.rune_free, rune_final: g.rune_final + g.rune_next }
}
#[cfg(test)]