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

+88 -2
View File
@@ -64,6 +64,7 @@ pub const D_RUBY: u32 = 122;
pub const D_DIAMOND: u32 = 123;
pub const D_RUBBLE: u32 = 124; // floor decals: pebbles and fragments
pub const D_CRACK: u32 = 125; // a long crack through several stones
pub const T_AERIE: u32 = 126; // 4 variants: straw, twigs and feathers
pub struct Gfx {
pub atlas: Texture2D,
@@ -726,6 +727,23 @@ fn gen_atlas() -> Image {
let h = hash(cu, cv, seed) % 3;
Some(if e { lo } else if h == 0 || (u % 4 == 1 && v % 4 == 1) { hi } else if d > 0.65 { lo } else { mid })
};
// aerie: straw and twigs on stone, a few feathers
for v in 0..4u32 {
cv.cell(T_AERIE + v, |x, y| {
let base = stones(x, y, v + 230, (70, 62, 58), (36, 30, 30), 8);
let twig = (x * 3 + y * 7 + v as i32 * 5) % 13 == 0 || (x * 5 - y * 2 + v as i32) % 17 == 0;
let straw = rnd(x / 2, y, v + 231) > 0.62;
if hash(x, y, v + 232) % 41 == 0 {
Some(c(235, 232, 225)) // feather
} else if twig {
Some(c(96, 66, 40))
} else if straw {
Some(shade((190, 160, 80), (rnd(x, y, v + 233) * 20.0) as i32 - 10))
} else {
Some(base)
}
});
}
// floor decals
cv.cell(D_RUBBLE, |x, y| {
for k in 0..9 {
@@ -890,6 +908,55 @@ fn sprite_rows(k: EK) -> [&'static str; 10] {
".kRRRRRRk.",
"..kkkkkk..",
],
// flying creatures
EK::Bat => [
"..........",
"k..k..k..k",
"Pk.kPPk.kP",
"PPkPPPPkPP",
"PPPPrPrPPP",
"PPPPPPPPPP",
".PPkPPkPP.",
"..k.PP.k..",
".....k....",
"..........",
],
EK::Gargoyle => [
"w........w",
"Cw.kkkk.wC",
"CCkceeckCC",
"CCkcccckCC",
"CkcccccckC",
"k.kcCCck.k",
".kcccccck.",
".kcCkkCck.",
".kck..kck.",
".kk....kk.",
],
EK::Owl => [
"..k....k..",
".kBk..kBk.",
".kbbbbbbk.",
"kbeebbeebk",
"kbekbbekbk",
".kbbyybbk.",
"kubbbbbbuk",
"kubbwwbbuk",
".kubbbbuk.",
"..ky..yk..",
],
EK::Wyrm => [
"R.......kw",
"RR....krrk",
"RRR..krekr",
"RRRRkrrrrk",
".RRrrrrok.",
"..rrooork.",
".krrooorr.",
"krrrrrrRk.",
"k.rrk.rk..",
"..kk..kk..",
],
// obsidian demon with glowing cracks, broken chains and twin axes
EK::Chainbreaker => [
"s.kkkkkk.s",
@@ -947,9 +1014,27 @@ fn pal(ch: char) -> Option<Color> {
})
}
pub const SPRITE_KINDS: [EK; 13] = [EK::Worker, EK::Kobold, EK::Spider, EK::Mage, EK::Ogre, EK::Knight, EK::Archer, EK::Priest, EK::Dwarf, EK::Paladin, EK::Jailer, EK::Priestess, EK::Chainbreaker];
pub const SPRITE_KINDS: [EK; 17] = [
EK::Worker,
EK::Kobold,
EK::Spider,
EK::Mage,
EK::Ogre,
EK::Knight,
EK::Archer,
EK::Priest,
EK::Dwarf,
EK::Paladin,
EK::Jailer,
EK::Priestess,
EK::Chainbreaker,
EK::Bat,
EK::Gargoyle,
EK::Owl,
EK::Wyrm,
];
// width of the sprite texture in pixels
pub const SPRITE_TEX_W: f32 = (13 * 12) as f32;
pub const SPRITE_TEX_W: f32 = (17 * 12) as f32;
pub const SPRITE_TEX_H: f32 = 24.0;
pub fn sprite_index(k: EK) -> u32 {
@@ -1143,6 +1228,7 @@ impl Gfx {
Kind::Bridge => T_BRIDGE + (v & 1),
Kind::LavaBridge => T_STONEBRIDGE + (v & 1),
Kind::Shard => T_HEROFLOOR + v,
Kind::Aerie => T_AERIE + v,
}
}