Aerie and flying creatures: bats, chain gargoyles, arcane owls, ember wyrms

- New room: aerie (key Y, rooms category, campaign from mission 7); flyers sleep there
- Arrivals: aerie 4+ bats; + torture chamber gargoyles; + library owls; big aerie and treasury
  (16+ each) a rare ember wyrm; portal checks free places in lairs and aerie separately
- Flyers cross water and lava (path finding, flag, attacks), are not burnt by lava, patrol far
  when idle, attack single enemies, flee to the aerie below 25% health and rest there up to 75%
- Owls shoot arcane bolts, the wyrm breathes fire with splash damage; shots from the air
- Hovering sprites with flapping, aerie floor, perches, roosts, plinths, twig and ember nests
- Room counts are recounted on load (no longer stored); tests for flying and arrivals

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 13:01:26 +01:00
1 parent 729dd4fe4c
commit b7d7fc2b52
10 files changed
+454 -56

No files matched your search

+50
View File
@@ -84,6 +84,12 @@ pub fn draw_props_solid(g: &Game, cam: &Cam, t: f32) {
bed(kind, c, h, l, t);
}
}
Kind::Aerie if tile.bed != 0 => {
if let Some(&kind) = u.beds.get(&tile.bed) {
bed(kind, c, h, l, t);
}
}
Kind::Aerie if h % 3 == 0 => perch(c, h, l),
Kind::Treasury if chest_tile(x, z) => chest(c, h, l, tile.gold as f32 / TREASURY_TILE, tile.res),
Kind::Training if (x + z) % 2 == 0 => training(c, h, l, t, u.busy.contains(&i) || u.busy.iter().any(|&b| adj8(b, i))),
Kind::Torture if (x + z) % 2 == 1 => torture(c, h, l, t, u.tortured.contains(&i) || u.tortured.iter().any(|&b| adj8(b, i))),
@@ -145,6 +151,16 @@ pub fn draw_props_glow(g: &Game, cam: &Cam, t: f32) {
}
}
}
Kind::Aerie if tile.bed != 0 => {
// the wyrm's nest glows
if let Some(EK::Wyrm) = u.beds.get(&tile.bed) {
for n in 0..5 {
let a = n as f32 * 1.3 + k;
let f = 0.6 + 0.4 * (t * 3.0 + a).sin();
draw_sphere(c + vec3(a.cos() * 0.2, 0.06, a.sin() * 0.2), 0.06 * f, None, Color::new(1.0, 0.5, 0.1, 0.9));
}
}
}
Kind::Lair if tile.bed != 0 => {
// the flame priestess sleeps on embers
if let Some(EK::Priestess) = u.beds.get(&tile.bed) {
@@ -285,6 +301,33 @@ fn bed(kind: EK, c: Vec3, h: u32, l: f32, t: f32) {
cube(at(0.0, 0.0, 0.05), vec3(0.6, 0.1, 0.48), rgb(150, 40, 30, l));
cube(at(0.0, 0.0, 0.11), vec3(0.4, 0.03, 0.3), rgb(220, 110, 40, l));
}
EK::Bat => {
// roost: a beam between two posts to hang from
for side in [-0.32f32, 0.32] {
cube(at(side, 0.0, 0.45), vec3(0.06, 0.9, 0.06), rgb(90, 64, 40, l));
}
cube(at(0.0, 0.0, 0.88), vec3(0.72, 0.06, 0.06), rgb(110, 78, 48, l));
}
EK::Gargoyle => {
// stone plinth to sit on
cube(at(0.0, 0.0, 0.2), vec3(0.5, 0.4, 0.5), rgb(110, 110, 118, l));
cube(at(0.0, 0.0, 0.42), vec3(0.58, 0.06, 0.58), rgb(130, 130, 138, l));
}
EK::Owl => {
// twig nest on a post
cube(at(0.0, 0.0, 0.3), vec3(0.08, 0.6, 0.08), rgb(90, 64, 40, l));
for n in 0..6 {
let a = n as f32 * 1.05 + rot;
cube(c + vec3(a.cos() * 0.16, 0.62, a.sin() * 0.16), vec3(0.16, 0.06, 0.05), rgb(110 + (n * 6) as u8, 80, 44, l));
}
}
EK::Wyrm => {
// a big nest of dark rocks (the embers glow in the glow pass)
for n in 0..9 {
let a = n as f32 * 0.7 + rot;
cube(c + vec3(a.cos() * 0.42, 0.09, a.sin() * 0.42), vec3(0.2, 0.18 + (n % 3) as f32 * 0.04, 0.2), rgb(60, 44, 44, l));
}
}
EK::Chainbreaker => {
// a hollow of broken rocks and a burst chain
for n in 0..7 {
@@ -308,6 +351,13 @@ fn bed(kind: EK, c: Vec3, h: u32, l: f32, t: f32) {
let _ = t;
}
// an empty perch in the aerie: post with a crossbar
fn perch(c: Vec3, h: u32, l: f32) {
let dx = ((h >> 4) % 3) as f32 * 0.12 - 0.12;
cube(c + vec3(dx, 0.35, 0.0), vec3(0.06, 0.7, 0.06), rgb(96, 68, 42, l));
cube(c + vec3(dx, 0.68, 0.0), vec3(0.4, 0.05, 0.05), rgb(110, 78, 48, l));
}
// ---------------------------------------------------------------- training hall
fn training(c: Vec3, h: u32, l: f32, t: f32, busy: bool) {