Badly hurt creatures may flee to their lair or the core and heal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 16:53:32 +01:00
1 parent 5701124c4f
commit 535800c7dc
1 file changed
+43 -13
+43 -13
View File
@@ -3132,14 +3132,20 @@ impl Game {
e.timer = 2.0 + self.rng.f() * 3.0; 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 { fn start_rest(&mut self, e: &mut Ent) -> bool {
let cur = e.tile(); let cur = e.tile();
let target = e.bed.filter(|&b| self.map.t[b].kind == Kind::Aerie).or_else(|| { let fly = flies(e.kind);
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 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 }; 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); e.job = Job::Rest(b);
true 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); 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% // badly hurt creatures may flee before they die (not all of them, and not at once)
if flies(e.kind) && e.hp < e.max_hp * 0.25 && !matches!(e.job, Job::Rest(_) | Job::Leave) { // 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); self.unreserve(me);
if self.start_rest(e) { if self.start_rest(e) {
return; return;
@@ -3570,17 +3577,18 @@ impl Game {
return; return;
} }
let (bx, by) = xy(b); 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; let there = (e.x - bx as f32 - 0.5).abs() < 1.5 && (e.y - by as f32 - 0.5).abs() < 1.5;
if !at_aerie { if !there {
// pushed away on the way: fly on, or give up if there is no way // pushed away on the way: go on, or give up if there is no way
match self.map.fly_path_to(e.tile(), b) { match self.map.creature_path(flies(e.kind), e.tile(), b) {
Some(p) if !p.is_empty() => e.path = p, Some(p) if !p.is_empty() => e.path = p,
_ => e.job = Job::None, _ => e.job = Job::None,
} }
return; return;
} }
// resting in the aerie heals quickly, back to work at 75% // resting heals: quickly in a bed room, slower next to the core; back to work at 75%
e.hp = (e.hp + e.max_hp * dt / 12.0).min(e.max_hp); 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); e.energy = (e.energy + dt / 14.0).min(1.0);
if e.hp >= e.max_hp * 0.75 { if e.hp >= e.max_hp * 0.75 {
e.job = Job::None; 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"); 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] #[test]
fn flyers_take_every_way_across_rivers() { fn flyers_take_every_way_across_rivers() {
let (mut g, rx) = river_game(false); let (mut g, rx) = river_game(false);