diff --git a/src/game.rs b/src/game.rs index f254e9b..969ef92 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3132,14 +3132,20 @@ impl Game { e.timer = 2.0 + self.rng.f() * 3.0; } - // badly hurt flyers flee to the aerie and rest until they are healed to 75% + // badly hurt creatures flee to their bed (lair, flyers: aerie), or else next to the soul + // core, and rest there until they are healed to 75% fn start_rest(&mut self, e: &mut Ent) -> bool { let cur = e.tile(); - let target = e.bed.filter(|&b| self.map.t[b].kind == Kind::Aerie).or_else(|| { - self.map.search(cur, self.map.fly_cost(), |i| self.map.t[i].kind == Kind::Aerie, true).map(|p| p.last().copied().unwrap_or(cur)) - }); + let fly = flies(e.kind); + let bed_kind = if fly { Kind::Aerie } else { Kind::Lair }; + let me = self.me; + let target = e + .bed + .filter(|&b| self.map.t[b].kind == bed_kind) + .or_else(|| self.map.search(cur, self.map.creature_cost(fly), |i| self.map.t[i].kind == bed_kind && self.map.t[i].owner == me, true).map(|p| p.last().copied().unwrap_or(cur))) + .or_else(|| self.map.search(cur, self.map.creature_cost(fly), |i| self.map.adj_core_of(i, me), true).map(|p| p.last().copied().unwrap_or(cur))); let Some(b) = target else { return false }; - e.path = self.map.fly_path_to(cur, b).unwrap_or_default(); + e.path = self.map.creature_path(fly, cur, b).unwrap_or_default(); e.job = Job::Rest(b); true } @@ -3511,8 +3517,9 @@ impl Game { self.fx(8, bx as f32 + 0.5, by as f32 + 0.5, 0.0, 0.0, 0.6, 0xff40d0ff); } } - // flyers flee when badly hurt and come back at 75% - if flies(e.kind) && e.hp < e.max_hp * 0.25 && !matches!(e.job, Job::Rest(_) | Job::Leave) { + // badly hurt creatures may flee before they die (not all of them, and not at once) + // and come back at 75% + if e.hp < e.max_hp * 0.25 && !matches!(e.job, Job::Rest(_) | Job::Leave) && self.rng.f() < 0.3 { self.unreserve(me); if self.start_rest(e) { return; @@ -3570,17 +3577,18 @@ impl Game { return; } let (bx, by) = xy(b); - let at_aerie = (e.x - bx as f32 - 0.5).abs() < 1.5 && (e.y - by as f32 - 0.5).abs() < 1.5; - if !at_aerie { - // pushed away on the way: fly on, or give up if there is no way - match self.map.fly_path_to(e.tile(), b) { + let there = (e.x - bx as f32 - 0.5).abs() < 1.5 && (e.y - by as f32 - 0.5).abs() < 1.5; + if !there { + // pushed away on the way: go on, or give up if there is no way + match self.map.creature_path(flies(e.kind), e.tile(), b) { Some(p) if !p.is_empty() => e.path = p, _ => e.job = Job::None, } return; } - // resting in the aerie heals quickly, back to work at 75% - e.hp = (e.hp + e.max_hp * dt / 12.0).min(e.max_hp); + // resting heals: quickly in a bed room, slower next to the core; back to work at 75% + let bed_room = matches!(self.map.t[b].kind, Kind::Lair | Kind::Aerie); + e.hp = (e.hp + e.max_hp * dt / if bed_room { 12.0 } else { 24.0 }).min(e.max_hp); e.energy = (e.energy + dt / 14.0).min(1.0); if e.hp >= e.max_hp * 0.75 { e.job = Job::None; @@ -6045,6 +6053,28 @@ mod tests { assert!(!g.torches.iter().any(|t| t.stand && t.wall == s0), "a stand on lost ground is removed"); } + #[test] + fn badly_hurt_creatures_flee_to_the_lair_and_heal() { + let mut g = Game::new(5, 64, 64); + let (cx, cy) = g.map.core; + g.gold = 5000.0; + g.build_rect(1, cx - 3, cy - 3, cx - 2, cy + 2); + assert!(g.counts[1] >= 8, "a lair"); + let id = g.spawn(EK::Kobold, false, cx as f32 + 5.5, cy as f32 + 0.5, 2); + let j = g.ents.iter().position(|e| e.id == id).unwrap(); + g.ents[j].hp = g.ents[j].max_hp * 0.2; + let mut fled = false; + for _ in 0..(20 * 6) { + g.update(0.05); + fled |= matches!(g.ents[j].job, Job::Rest(_)); + } + assert!(fled, "it fled to rest"); + for _ in 0..(20 * 20) { + g.update(0.05); + } + assert!(g.ents[j].hp > g.ents[j].max_hp * 0.5, "and healed: {} of {}", g.ents[j].hp, g.ents[j].max_hp); + } + #[test] fn flyers_take_every_way_across_rivers() { let (mut g, rx) = river_game(false);