Hero waves break in where the warning shows: they round the realm to the marked entry; the entry tile glows red
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dab31579df
commit
bd3f42b8b8
3 files changed
+73
-4
No files matched your search
@@ -209,3 +209,41 @@ fn balance_waves() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// where the heroes of a wave really break in, against the marked entry points
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn balance_entry_points() {
|
||||||
|
use crate::map::*;
|
||||||
|
println!("\n== planned entry vs. where the heroes step onto the master's ground ==");
|
||||||
|
for seed in seeds() {
|
||||||
|
let mut g = Game::new(seed, 96, 96);
|
||||||
|
pilot(&mut g, 1, None);
|
||||||
|
let mut planned: Vec<(i32, i32)> = Vec::new();
|
||||||
|
let mut first_in: Option<(i32, i32)> = None;
|
||||||
|
let mut wave = 0;
|
||||||
|
for _ in 0..((20.0 * 60.0 / STEP) as usize) {
|
||||||
|
g.update(STEP);
|
||||||
|
if g.state != 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !g.planned_entries.is_empty() {
|
||||||
|
planned = g.planned_entries.clone();
|
||||||
|
}
|
||||||
|
if g.wave != wave {
|
||||||
|
wave = g.wave;
|
||||||
|
first_in = None;
|
||||||
|
}
|
||||||
|
if first_in.is_none() {
|
||||||
|
if let Some(e) = g.ents.iter().find(|e| e.alive && e.active_hero() && g.map.get(e.x as i32, e.y as i32).map_or(false, |t| t.owner == Owner::Player)) {
|
||||||
|
first_in = Some((e.x as i32, e.y as i32));
|
||||||
|
let d = planned.iter().map(|&(px, py)| (((px - e.x as i32).pow(2) + (py - e.y as i32).pow(2)) as f32).sqrt()).fold(f32::MAX, f32::min);
|
||||||
|
let wp = g.ents.iter().filter(|e| e.alive && e.active_hero()).filter(|e| e.waypoint.is_some()).count();
|
||||||
|
let heroes = g.ents.iter().filter(|e| e.alive && e.active_hero()).count();
|
||||||
|
let job = format!("{:?}", e.job);
|
||||||
|
println!("seed {} wave {}: planned {:?} first in at {:?} -> {:.1} tiles off; heroes with waypoint {}/{}; its job {}", seed, wave, planned, first_in.unwrap(), d, wp, heroes, job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-3
@@ -4903,6 +4903,13 @@ impl Game {
|
|||||||
if (e.x - wx as f32 - 0.5).abs() < 2.5 && (e.y - wy as f32 - 0.5).abs() < 2.5 {
|
if (e.x - wx as f32 - 0.5).abs() < 2.5 && (e.y - wy as f32 - 0.5).abs() < 2.5 {
|
||||||
e.waypoint = None; // broke into the realm: now for the core
|
e.waypoint = None; // broke into the realm: now for the core
|
||||||
e.path.clear();
|
e.path.clear();
|
||||||
|
} else if let Some(&next) = e.path.first() {
|
||||||
|
// the realm grew into the way (imps claim fast): round it again
|
||||||
|
let realm = self.map.t[idx(wx, wy)].owner;
|
||||||
|
if self.map.t[next].owner == realm && next != idx(wx, wy) && self.map.t[e.tile()].owner != realm {
|
||||||
|
e.path.clear();
|
||||||
|
e.repath = 0.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.path.is_empty() && e.repath <= 0.0 {
|
if e.path.is_empty() && e.repath <= 0.0 {
|
||||||
@@ -4910,7 +4917,8 @@ impl Game {
|
|||||||
let path = match e.waypoint {
|
let path = match e.waypoint {
|
||||||
Some((wx, wy)) => {
|
Some((wx, wy)) => {
|
||||||
let goal = idx(wx, wy);
|
let goal = idx(wx, wy);
|
||||||
let p = self.map.search(cur, self.map.hero_cost(), |i| i == goal, true);
|
// round the realm from outside: in through the marked entry, not before
|
||||||
|
let p = self.entry_path(cur, goal).or_else(|| self.map.search(cur, self.map.hero_cost(), |i| i == goal, true));
|
||||||
if p.is_none() {
|
if p.is_none() {
|
||||||
e.waypoint = None;
|
e.waypoint = None;
|
||||||
}
|
}
|
||||||
@@ -4975,14 +4983,28 @@ impl Game {
|
|||||||
scored.push(((x, y), score));
|
scored.push(((x, y), score));
|
||||||
}
|
}
|
||||||
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
let first = scored[0].0;
|
// only places the heroes can reach from the gate without crossing the realm first
|
||||||
|
let gate = idx(gx, gy);
|
||||||
|
let mut tried = 0;
|
||||||
|
let mut ok = |g: &Game, p: (i32, i32)| {
|
||||||
|
tried += 1;
|
||||||
|
tried > 12 || g.entry_path(gate, idx(p.0, p.1)).is_some()
|
||||||
|
};
|
||||||
|
let Some(first) = scored.iter().map(|s| s.0).find(|&p| ok(self, p)) else { return };
|
||||||
self.planned_entries.push(first);
|
self.planned_entries.push(first);
|
||||||
// second entry point at least 10 tiles away (used by waves of 6+ heroes)
|
// second entry point at least 10 tiles away (used by waves of 6+ heroes)
|
||||||
if let Some(&(second, _)) = scored.iter().find(|((x, y), _)| (x - first.0).pow(2) + (y - first.1).pow(2) >= 100) {
|
if let Some(second) = scored.iter().map(|s| s.0).filter(|&(x, y)| (x - first.0).pow(2) + (y - first.1).pow(2) >= 100).find(|&p| ok(self, p)) {
|
||||||
self.planned_entries.push(second);
|
self.planned_entries.push(second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a hero's way to an entry tile that stays off the realm (the master's ground) until the entry
|
||||||
|
pub(crate) fn entry_path(&self, from: usize, goal: usize) -> Option<Vec<usize>> {
|
||||||
|
let owner = self.map.t[goal].owner;
|
||||||
|
let cost = self.map.hero_cost();
|
||||||
|
self.map.search(from, |i| if i != goal && i != from && self.map.t[i].owner == owner { None } else { cost(i) }, |i| i == goal, true)
|
||||||
|
}
|
||||||
|
|
||||||
// compass direction of a tile as seen from the core (language key)
|
// compass direction of a tile as seen from the core (language key)
|
||||||
pub fn direction_key(&self, (x, y): (i32, i32)) -> &'static str {
|
pub fn direction_key(&self, (x, y): (i32, i32)) -> &'static str {
|
||||||
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
|
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
|
||||||
|
|||||||
+10
-1
@@ -3377,12 +3377,21 @@ fn draw_entry_warnings(g: &Game, t: f32, n: usize) {
|
|||||||
let line = [255, 90, 60, (120.0 + 120.0 * p) as u8];
|
let line = [255, 90, 60, (120.0 + 120.0 * p) as u8];
|
||||||
let mut mb = MB::new();
|
let mut mb = MB::new();
|
||||||
for &(ex, ey) in &g.planned_entries {
|
for &(ex, ey) in &g.planned_entries {
|
||||||
|
let realm = g.map.get(ex, ey).map_or(Owner::Player, |t| t.owner);
|
||||||
|
// the entry tile itself: a pulsing red plate (also where an open corridor leads in)
|
||||||
|
let c = vec3(ex as f32 + 0.5, 0.03, ey as f32 + 0.5);
|
||||||
|
mb.quad(
|
||||||
|
[c + vec3(-0.5, 0.0, -0.5), c + vec3(0.5, 0.0, -0.5), c + vec3(0.5, 0.0, 0.5), c + vec3(-0.5, 0.0, 0.5)],
|
||||||
|
[Vec2::ZERO; 4],
|
||||||
|
[col(vec3(1.0, 0.16, 0.12) * (0.6 + 0.6 * p), 0.45 + 0.35 * p); 4],
|
||||||
|
[Vec3::Y; 4],
|
||||||
|
);
|
||||||
for dy in -2..=2 {
|
for dy in -2..=2 {
|
||||||
for dx in -2..=2 {
|
for dx in -2..=2 {
|
||||||
let (x, y) = (ex + dx, ey + dy);
|
let (x, y) = (ex + dx, ey + dy);
|
||||||
let Some(tile) = g.map.get(x, y) else { continue };
|
let Some(tile) = g.map.get(x, y) else { continue };
|
||||||
// walls of the realm's border near the entry point (not rock)
|
// walls of the realm's border near the entry point (not rock)
|
||||||
let border = DIR4.iter().any(|&(ax, ay)| g.map.get(x + ax, y + ay).map_or(false, |o| o.owner == Owner::Player && !o.solid()));
|
let border = DIR4.iter().any(|&(ax, ay)| g.map.get(x + ax, y + ay).map_or(false, |o| o.owner == realm && !o.solid()));
|
||||||
if tile.diggable() && border && dx.abs() + dy.abs() <= 3 && mb.v.len() < 30000 {
|
if tile.diggable() && border && dx.abs() + dy.abs() <= 3 && mb.v.len() < 30000 {
|
||||||
block_shell(&mut mb, &g.map, x, y, n, fill, line, 1.0, 0.04);
|
block_shell(&mut mb, &g.map, x, y, n, fill, line, 1.0, 0.04);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user