Compare commits

...
2 Commits
4 changed files with 74 additions and 5 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
v202611-00074
v202611-00075
+38
View File
@@ -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
View File
@@ -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 {
e.waypoint = None; // broke into the realm: now for the core
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 {
@@ -4910,7 +4917,8 @@ impl Game {
let path = match e.waypoint {
Some((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() {
e.waypoint = None;
}
@@ -4975,14 +4983,28 @@ impl Game {
scored.push(((x, y), score));
}
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);
// 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);
}
}
// 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)
pub fn direction_key(&self, (x, y): (i32, i32)) -> &'static str {
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
+10 -1
View File
@@ -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 mut mb = MB::new();
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 dx in -2..=2 {
let (x, y) = (ex + dx, ey + dy);
let Some(tile) = g.map.get(x, y) else { continue };
// 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 {
block_shell(&mut mb, &g.map, x, y, n, fill, line, 1.0, 0.04);
}