Hero waves march on the strongest master

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 20:58:46 +01:00
1 parent 988f4a3333
commit e3a9d32d61
22 files changed
+147 -22

No files matched your search

+77 -21
View File
@@ -1700,6 +1700,13 @@ impl Game {
self.wave_timer -= dt;
if !self.warned && self.wave_timer < 30.0 {
self.warned = true;
let goal = self.wave_goal();
if goal != self.me_side {
// the wave will march on another master: no entry markers in this dungeon
self.planned_entries.clear();
self.announce_wave("msg.wave_warn", "msg.wave_warn_on_master", vec![(self.wave + 1).to_string()], goal, Sfx::WaveAlarm);
return;
}
self.plan_entries();
if self.planned_entries.is_empty() {
self.msg("msg.wave_warn", vec![(self.wave + 1).to_string()]);
@@ -2084,12 +2091,14 @@ impl Game {
}
// Endless waves: strength 60% -> 100% of the army value, with some randomness, at least 2 knights.
fn spawn_wave(&mut self) {
pub(crate) fn spawn_wave(&mut self) {
use EK::*;
let w = self.wave + 1;
let ratio = (0.6 + 0.05 * (w - 1) as f32).min(1.0) * (0.88 + self.rng.f() * 0.24);
let min_value = Self::squad_value(&[(Knight, 1), (Knight, 1)]);
let target = (self.army_value() * ratio).max(min_value);
// the humans march on the strongest master; the wave is measured against him
let goal = self.wave_goal();
let target = (self.side_army(goal) * ratio).max(min_value);
let mut pool = vec![(Knight, 4), (Archer, 3)];
if w >= 2 {
pool.push((Dwarf, 2));
@@ -2110,21 +2119,20 @@ impl Game {
self.last_wave_value = Self::squad_value(&list);
let before = self.ents.len();
self.spawn_squad(&list, false);
// the humans attack everybody: now and then a rival master instead of the player
let alive: Vec<u8> = self.rivals.iter().filter(|r| r.alive).map(|r| r.side).collect();
if !alive.is_empty() {
let r = (self.rng.next() as usize) % (alive.len() + 2);
if r >= 2 {
let side = alive[r - 2];
for e in self.ents[before..].iter_mut() {
e.target = Some(side);
e.waypoint = None;
}
self.planned_entries.clear();
self.sfx_ui(Sfx::WaveStart);
self.msg(if side == 2 { "msg.wave_on_rival1" } else { "msg.wave_on_rival2" }, vec![w.to_string(), list.len().to_string()]);
return;
if goal != self.me_side {
for e in self.ents[before..].iter_mut() {
e.target = Some(goal);
e.waypoint = None;
}
self.planned_entries.clear();
let args = vec![w.to_string(), list.len().to_string()];
if self.is_master(goal) {
self.announce_wave("msg.wave", "msg.wave_on_master", args, goal, Sfx::WaveStart);
} else {
self.sfx_ui(Sfx::WaveStart);
self.msg(if goal == 2 { "msg.wave_on_rival1" } else { "msg.wave_on_rival2" }, args);
}
return;
}
if self.planned_entries.is_empty() {
self.plan_entries();
@@ -2145,12 +2153,60 @@ impl Game {
self.entry_history.drain(0..n - 4);
}
}
self.sfx_ui(Sfx::WaveStart);
if boss.is_some() {
self.msg("msg.wave_boss", vec![w.to_string(), list.len().to_string()]);
} else {
self.msg("msg.wave", vec![w.to_string(), list.len().to_string()]);
let key = if boss.is_some() { "msg.wave_boss" } else { "msg.wave" };
let goal = self.me_side;
self.announce_wave(key, "msg.wave_on_master", vec![w.to_string(), list.len().to_string()], goal, Sfx::WaveStart);
}
// The side a hero wave marches on: the strongest master still standing (creatures, towers and
// gold), players and rival masters alike; ties go to the lower side.
pub fn wave_goal(&self) -> u8 {
let mut best = (self.me_side, f32::MIN);
let mut sides: Vec<u8> = self.master_sides().into_iter().filter(|&s| !self.is_out(s) && self.core_hp_of(s) > 0.0).collect();
sides.extend(self.rivals.iter().filter(|r| r.alive).map(|r| r.side));
sides.sort_unstable();
for s in sides {
let gold = if self.is_master(s) { self.gold_of(s) } else { 0.0 };
let score = self.side_army(s) + 0.05 * gold;
if score > best.1 {
best = (s, score);
}
}
best.0
}
// the fighting strength of one side, like `army_value`: its creatures and (half) its towers
pub fn side_army(&self, side: u8) -> f32 {
let owner = Owner::of_side(side);
let (mut h, mut d) = (0.0, 0.0);
for e in &self.ents {
if e.alive && !e.hero && !e.chained && e.kind != EK::Worker && e.side() == side {
h += e.max_hp;
d += e.atk() / st(e.kind).interval;
}
}
for t in self.towers.iter().filter(|t| !t.hero && self.map.t[t.tile].owner == owner) {
h += 0.5 * t.max_hp();
d += 0.5 * t.dmg() / tower_stats(t.kind).interval;
}
(h * d).sqrt()
}
// tells about a wave: the attacked master hears `key`; every other player of a match hears
// `other` (with the attacked master's colour as last argument). A wave on a rival master is
// told to the player like a normal warning.
fn announce_wave(&mut self, key: &'static str, other: &'static str, args: Vec<String>, goal: u8, sfx: Sfx) {
if !self.is_master(goal) || self.netplay.is_none() || self.local_side == goal {
let me = self.me_side;
self.switch_to(goal);
self.msg(key, args);
self.sfx_ui(sfx);
self.switch_to(me);
return;
}
let mut a = args;
a.push(format!("@net.side{goal}"));
self.tell_local(other, a, Some(sfx));
}
// defenders at 75 / 50 / 25 % gate HP