Hero waves march on the strongest master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
988f4a3333
commit
e3a9d32d61
22 files changed
+147
-22
No files matched your search
+77
-21
@@ -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
|
||||
|
||||
Reference in new issue
Block a user