Let flyers path over water and lava everywhere
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
574660e173
commit
1ef2239fbf
7 files changed
+85
-23
No files matched your search
+1
-1
@@ -195,7 +195,7 @@ impl Game {
|
||||
e.repath = 3.0 + self.rng.f() * 4.0;
|
||||
let (tx, ty) = if from_home(e.x, e.y) > 3.0 { (hx, hy) } else { (hx + self.rng.f() * 4.0 - 2.0, hy + self.rng.f() * 4.0 - 2.0) };
|
||||
if self.map.walk(tx as i32, ty as i32) {
|
||||
if let Some(p) = self.map.path_to(e.tile(), idx(tx as i32, ty as i32)) {
|
||||
if let Some(p) = self.map.creature_path(flies(e.kind), e.tile(), idx(tx as i32, ty as i32)) {
|
||||
e.path = p;
|
||||
}
|
||||
}
|
||||
|
||||
+47
-10
@@ -1692,7 +1692,7 @@ impl Game {
|
||||
}
|
||||
let cur = e.tile();
|
||||
let near = |i: usize| tasks.iter().copied().find(|&t| i != t && adj4(i, t) || (i == t && self.map.t[t].walkable()));
|
||||
let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| near(i).is_some(), true) else { return false };
|
||||
let Some(p) = self.map.search(cur, self.map.creature_cost(flies(e.kind)), |i| near(i).is_some(), true) else { return false };
|
||||
let end = p.last().copied().unwrap_or(cur);
|
||||
let Some(t) = near(end) else { return false };
|
||||
e.path = p;
|
||||
@@ -3387,10 +3387,10 @@ impl Game {
|
||||
|
||||
// ---------------------------------------------------------------- creatures
|
||||
|
||||
fn find_room_tile(&self, cur: usize, kind: Kind, me: u32, need_food: bool) -> Option<Vec<usize>> {
|
||||
fn find_room_tile(&self, e: &Ent, kind: Kind, me: u32, need_food: bool) -> Option<Vec<usize>> {
|
||||
self.map.search(
|
||||
cur,
|
||||
self.map.walk_cost(),
|
||||
e.tile(),
|
||||
self.map.creature_cost(flies(e.kind)),
|
||||
|i| {
|
||||
let t = &self.map.t[i];
|
||||
t.kind == kind && t.owner == self.me && (t.reserved == 0 || t.reserved == me) && (!need_food || t.food >= 1.0)
|
||||
@@ -3905,7 +3905,7 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
if e.hunger > 1.0 && !(self.flag.is_some() && e.hunger < 1.5) {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Farm, me, true) {
|
||||
if let Some(p) = self.find_room_tile(e, Kind::Farm, me, true) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
e.path = p;
|
||||
@@ -3939,7 +3939,7 @@ impl Game {
|
||||
if self.counts[6] > 0 {
|
||||
let r2 = self.rng.f();
|
||||
if r2 < 0.3 || (e.kind == EK::Priestess && r2 < 0.7) {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Temple, me, false) {
|
||||
if let Some(p) = self.find_room_tile(e, Kind::Temple, me, false) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
e.path = p;
|
||||
@@ -3974,7 +3974,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if self.counts[crate::rooms::ROOM_ARENA] > 0 && e.hp > e.max_hp * 0.8 && self.rng.f() < 0.25 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Arena, me, false) {
|
||||
if let Some(p) = self.find_room_tile(e, Kind::Arena, me, false) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
e.path = p;
|
||||
@@ -3987,7 +3987,7 @@ impl Game {
|
||||
if self.forge_orders() && self.counts[ROOM_FORGE] > 0 {
|
||||
let r = self.rng.f();
|
||||
if (e.kind == EK::Cogling && r < 0.9) || r < 0.25 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Forge, me, false) {
|
||||
if let Some(p) = self.find_room_tile(e, Kind::Forge, me, false) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
e.path = p;
|
||||
@@ -3999,7 +3999,7 @@ impl Game {
|
||||
}
|
||||
let r = self.rng.f();
|
||||
if (e.kind == EK::Mage && r < 0.8) || r < 0.2 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Library, me, false) {
|
||||
if let Some(p) = self.find_room_tile(e, Kind::Library, me, false) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
e.path = p;
|
||||
@@ -4009,7 +4009,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if r < 0.8 && self.gold >= 5.0 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Training, me, false) {
|
||||
if let Some(p) = self.find_room_tile(e, Kind::Training, me, false) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
e.path = p;
|
||||
@@ -5935,6 +5935,43 @@ mod tests {
|
||||
assert!(best <= 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flyers_take_every_way_across_rivers() {
|
||||
let (mut g, rx) = river_game(false);
|
||||
let (cx, cy) = g.map.core;
|
||||
// open floor on the far bank with one training tile of the player
|
||||
for y in cy - 2..=cy + 2 {
|
||||
for x in rx + 3..rx + 7 {
|
||||
let t = &mut g.map.t[idx(x, y)];
|
||||
t.kind = Kind::Floor;
|
||||
t.owner = Owner::None;
|
||||
}
|
||||
}
|
||||
let room = idx(rx + 4, cy);
|
||||
g.map.t[room].kind = Kind::Training;
|
||||
g.map.t[room].owner = g.me;
|
||||
let owl = g.spawn(EK::Owl, false, (cx + 4) as f32 + 0.5, cy as f32 + 0.5, 1);
|
||||
let kobold = g.spawn(EK::Kobold, false, (cx + 4) as f32 + 0.5, cy as f32 + 0.5, 1);
|
||||
let owl = g.ents.iter().find(|e| e.id == owl).unwrap().clone();
|
||||
let kobold = g.ents.iter().find(|e| e.id == kobold).unwrap().clone();
|
||||
// room searches: the owl flies over the river to the training room, the kobold cannot get there
|
||||
let p = g.find_room_tile(&owl, Kind::Training, owl.id + 1, false).expect("the owl finds the room across the river");
|
||||
assert_eq!(p.last().copied(), Some(room));
|
||||
assert!(g.find_room_tile(&kobold, Kind::Training, kobold.id + 1, false).map_or(true, |p| p.last() != Some(&room)));
|
||||
// flyers cut corners over water: a diagonal crossing is as short as on open ground
|
||||
let (a, b) = (idx(rx - 1, cy - 1), idx(rx + 3, cy + 1));
|
||||
let p = g.map.creature_path(true, a, b).expect("flight path");
|
||||
assert!(p.len() <= 5, "diagonal flight over the river: {} steps", p.len());
|
||||
assert!(p.iter().any(|&i| g.map.t[i].liquid()));
|
||||
// attacking flyers (the fairy spy) also fly over rivers instead of searching for a bridge
|
||||
assert!(g.map.hero_cost_as(true)(idx(rx + 1, cy)).is_some());
|
||||
assert_eq!(g.map.hero_cost()(idx(rx + 1, cy)), None);
|
||||
// a locked door stops flyers too
|
||||
g.map.t[idx(rx + 5, cy)].kind = Kind::Door;
|
||||
g.map.t[idx(rx + 5, cy)].res = 1;
|
||||
assert_eq!(g.map.fly_cost()(idx(rx + 5, cy)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flyers_cross_rivers_flee_and_heal() {
|
||||
let (mut g, rx) = river_game(true);
|
||||
|
||||
+31
-6
@@ -656,7 +656,9 @@ impl Map {
|
||||
let mut step = c * 10;
|
||||
if k >= 4 {
|
||||
// diagonal only if both neighbours are walkable (no corner cutting)
|
||||
if c > 1 || !self.walk(x + dx, y) || !self.walk(x, y + dy) {
|
||||
// (judged with the same cost, so flyers may cut corners over water and lava)
|
||||
let open = |cx: i32, cy: i32| cost(idx(cx, cy)) == Some(1);
|
||||
if c > 1 || !open(x + dx, y) || !open(x, y + dy) {
|
||||
continue;
|
||||
}
|
||||
step = 14;
|
||||
@@ -673,9 +675,22 @@ impl Map {
|
||||
}
|
||||
|
||||
pub fn walk_cost(&self) -> impl Fn(usize) -> Option<u32> + '_ {
|
||||
self.creature_cost(false)
|
||||
}
|
||||
|
||||
// flying creatures also cross water and lava
|
||||
pub fn fly_cost(&self) -> impl Fn(usize) -> Option<u32> + '_ {
|
||||
self.creature_cost(true)
|
||||
}
|
||||
|
||||
// the dungeon's creatures walk on open ground; flyers also cross water and lava
|
||||
pub fn creature_cost(&self, fly: bool) -> impl Fn(usize) -> Option<u32> + '_ {
|
||||
move |i| {
|
||||
let t = &self.t[i];
|
||||
// a locked door stops the dungeon's own creatures too
|
||||
if fly && t.liquid() {
|
||||
return Some(1);
|
||||
}
|
||||
// a locked door stops the dungeon's own creatures too (flyers included)
|
||||
if t.walkable() && !(t.kind == Kind::Door && t.res & 1 == 1) {
|
||||
Some(1)
|
||||
} else {
|
||||
@@ -684,9 +699,9 @@ impl Map {
|
||||
}
|
||||
}
|
||||
|
||||
// flying creatures also cross water and lava
|
||||
pub fn fly_cost(&self) -> impl Fn(usize) -> Option<u32> + '_ {
|
||||
move |i| if self.t[i].walkable() || self.t[i].liquid() { Some(1) } else { None }
|
||||
// path for a walking or flying creature
|
||||
pub fn creature_path(&self, fly: bool, start: usize, target: usize) -> Option<Vec<usize>> {
|
||||
self.search(start, self.creature_cost(fly), |i| i == target, true)
|
||||
}
|
||||
|
||||
pub fn fly_path_to(&self, start: usize, target: usize) -> Option<Vec<usize>> {
|
||||
@@ -694,9 +709,15 @@ impl Map {
|
||||
}
|
||||
|
||||
pub fn hero_cost(&self) -> impl Fn(usize) -> Option<u32> + '_ {
|
||||
self.hero_cost_as(false)
|
||||
}
|
||||
|
||||
// attackers dig through earth; flying ones also cross water and lava
|
||||
pub fn hero_cost_as(&self, fly: bool) -> impl Fn(usize) -> Option<u32> + '_ {
|
||||
move |i| {
|
||||
let tl = &self.t[i];
|
||||
match tl.kind {
|
||||
Kind::Water | Kind::Lava if fly => Some(1),
|
||||
Kind::Earth if tl.reinforced => Some(70),
|
||||
Kind::Steel => Some(300),
|
||||
Kind::Earth => Some(7),
|
||||
@@ -720,7 +741,11 @@ impl Map {
|
||||
}
|
||||
|
||||
pub fn hero_path_to_core(&self, start: usize) -> Option<Vec<usize>> {
|
||||
self.search(start, self.hero_cost(), |i| self.adj_core(i), true)
|
||||
self.hero_path_to_core_as(false, start)
|
||||
}
|
||||
|
||||
pub fn hero_path_to_core_as(&self, fly: bool, start: usize) -> Option<Vec<usize>> {
|
||||
self.search(start, self.hero_cost_as(fly), |i| self.adj_core(i), true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ impl Game {
|
||||
let mut n = 0;
|
||||
for (&j, &spot) in cands.iter().zip(spots.iter()).take(GUARDS) {
|
||||
let cur = self.ents[j].tile();
|
||||
let Some(p) = self.map.path_to(cur, spot) else { continue };
|
||||
let Some(p) = self.map.creature_path(flies(self.ents[j].kind), cur, spot) else { continue };
|
||||
let me = self.ents[j].id + 1;
|
||||
self.unreserve(me);
|
||||
self.map.t[spot].reserved = me;
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ impl Game {
|
||||
if d > 1.1 {
|
||||
if e.path.is_empty() || e.repath <= 0.0 {
|
||||
e.repath = 0.8;
|
||||
if let Some(p) = self.map.path_to(e.tile(), idx(tx as i32, ty as i32)) {
|
||||
if let Some(p) = self.map.creature_path(flies(e.kind), e.tile(), idx(tx as i32, ty as i32)) {
|
||||
e.path = p;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -366,7 +366,7 @@ impl Game {
|
||||
e.repath = 1.5;
|
||||
// attackers dig their way like heroes; the player's creatures only walk
|
||||
let goal = |i: usize| self.target_core_adjacent(i, target);
|
||||
let found = if e.side() == 0 { self.map.search(cur, self.map.walk_cost(), goal, true) } else { self.map.search(cur, self.map.hero_cost(), goal, true) };
|
||||
let found = if e.side() == 0 { self.map.search(cur, self.map.creature_cost(flies(e.kind)), goal, true) } else { self.map.search(cur, self.map.hero_cost_as(flies(e.kind)), goal, true) };
|
||||
match found {
|
||||
Some(p) => e.path = p,
|
||||
None => {
|
||||
@@ -411,7 +411,7 @@ impl Game {
|
||||
e.repath = 3.0 + self.rng.f() * 3.0;
|
||||
let (tx, ty) = if d2 > 36.0 { (hx, hy) } else { (hx + self.rng.f() * 8.0 - 4.0, hy + self.rng.f() * 8.0 - 4.0) };
|
||||
if self.map.walk(tx as i32, ty as i32) {
|
||||
if let Some(p) = self.map.path_to(e.tile(), idx(tx as i32, ty as i32)) {
|
||||
if let Some(p) = self.map.creature_path(flies(e.kind), e.tile(), idx(tx as i32, ty as i32)) {
|
||||
e.path = p;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -263,7 +263,7 @@ impl Game {
|
||||
CRATED => {
|
||||
if cur != crate_tile {
|
||||
if e.path.is_empty() {
|
||||
match self.map.path_to(cur, crate_tile) {
|
||||
match self.map.creature_path(flies(e.kind), cur, crate_tile) {
|
||||
Some(p) => e.path = p,
|
||||
None => {
|
||||
self.traps[k].carrier = 0;
|
||||
@@ -279,7 +279,7 @@ impl Game {
|
||||
CARRIED => {
|
||||
if cur != tile {
|
||||
if e.path.is_empty() {
|
||||
match self.map.path_to(cur, tile) {
|
||||
match self.map.creature_path(flies(e.kind), cur, tile) {
|
||||
Some(p) => e.path = p,
|
||||
None => {
|
||||
// no way there: the crate goes back into the forge
|
||||
|
||||
Reference in new issue
Block a user