Title demo as a showcase: doors, every trap, rune tower, rooms, mana spring, hero gate, spells
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fa61a00e9e
commit
4b57157e9c
2 files changed
+160
-12
No files matched your search
+151
-12
@@ -1,6 +1,10 @@
|
||||
// The endless battle behind the title menu: the dungeon's creatures and the knights fight on
|
||||
// a tongue of rock between a lava river (north) and an underground river (south). Both sides get balanced reinforcements, destroyed towers
|
||||
// are rebuilt after a while. Nothing here is ever saved.
|
||||
// The endless battle behind the title menu, a showcase of the game: the dungeon's creatures and
|
||||
// the knights fight on a tongue of rock between a lava river (north) and an underground river
|
||||
// (south). The dungeon side has a wall with a wooden, an iron and a magic door, rooms behind it
|
||||
// (treasury, forge, library, lair, ...), a rune forge tower, a magic tower and a mana spring; all
|
||||
// seven traps lie on the knights' way; the knights have their towers and the hero gate. Both sides
|
||||
// get balanced reinforcements, destroyed towers, doors and traps come back after a while, and now
|
||||
// and then the dungeon casts a spell. Nothing here is ever saved.
|
||||
|
||||
use crate::game::*;
|
||||
use crate::map::*;
|
||||
@@ -21,11 +25,15 @@ const REBUILD: f32 = 25.0; // seconds until a destroyed tower stands again
|
||||
pub struct Demo {
|
||||
spawn_t: f32,
|
||||
towers: Vec<(i32, i32, TowerKind, bool, f32)>, // slot: position, kind, knight tower, time since lost
|
||||
traps: Vec<(usize, crate::traps::TrapKind, f32)>, // slot: tile, trap or door, time since lost
|
||||
spell_t: f32,
|
||||
rng: Rng,
|
||||
}
|
||||
|
||||
const CREATURES: [(EK, u32); 9] = [(EK::Kobold, 4), (EK::Spider, 3), (EK::Mage, 2), (EK::Ogre, 2), (EK::Jailer, 1), (EK::Priestess, 1), (EK::Bat, 2), (EK::Gargoyle, 1), (EK::Owl, 1)];
|
||||
const KNIGHTS: [(EK, u32); 5] = [(EK::Knight, 5), (EK::Archer, 3), (EK::Priest, 2), (EK::Dwarf, 2), (EK::Paladin, 1)];
|
||||
const WALL_X: i32 = RIVER_X - 8; // the dungeon's wall with its three doors
|
||||
|
||||
const CREATURES: [(EK, u32); 12] = [(EK::Kobold, 4), (EK::Spider, 3), (EK::Mage, 2), (EK::Ogre, 2), (EK::Jailer, 1), (EK::Priestess, 1), (EK::Bat, 2), (EK::Gargoyle, 1), (EK::Owl, 1), (EK::Wyrm, 1), (EK::Cogling, 1), (EK::Revenant, 1)];
|
||||
const KNIGHTS: [(EK, u32); 8] = [(EK::Knight, 5), (EK::Archer, 3), (EK::Priest, 2), (EK::Dwarf, 2), (EK::Paladin, 1), (EK::Giant, 1), (EK::Fairy, 1), (EK::BattleMage, 1)];
|
||||
|
||||
fn pick(rng: &mut Rng, pool: &[(EK, u32)]) -> EK {
|
||||
let total: u32 = pool.iter().map(|p| p.1).sum();
|
||||
@@ -119,8 +127,34 @@ impl Game {
|
||||
}
|
||||
}
|
||||
}
|
||||
// a few rooms on the dungeon side for the look of it
|
||||
for (x0, y0, x1, y1, k) in [(6, 15, 11, 19, Kind::Lair), (9, 29, 14, 33, Kind::Training), (16, 13, 20, 16, Kind::Farm)] {
|
||||
// the dungeon's wall from top to bottom, with a door in three gaps
|
||||
for y in 2..H as i32 - 2 {
|
||||
let t = &mut g.map.t[idx(WALL_X, y)];
|
||||
if !t.solid() {
|
||||
t.kind = Kind::Earth;
|
||||
t.reinforced = true;
|
||||
t.owner = Owner::Player;
|
||||
}
|
||||
}
|
||||
let doors = [(BRIDGE_Y - 5, crate::traps::TrapKind::WoodDoor), (BRIDGE_Y + 1, crate::traps::TrapKind::IronDoor), (BRIDGE_Y + 7, crate::traps::TrapKind::MagicDoor)];
|
||||
for &(y, _) in doors.iter() {
|
||||
for x in WALL_X - 1..=WALL_X + 1 {
|
||||
let t = &mut g.map.t[idx(x, y)];
|
||||
t.kind = Kind::Floor;
|
||||
t.reinforced = false;
|
||||
t.owner = Owner::Player;
|
||||
}
|
||||
}
|
||||
// a few rooms on the dungeon side, the showy ones right behind the wall
|
||||
for (x0, y0, x1, y1, k) in [
|
||||
(WALL_X - 5, BRIDGE_Y - 3, WALL_X - 2, BRIDGE_Y - 1, Kind::Treasury),
|
||||
(WALL_X - 5, BRIDGE_Y + 3, WALL_X - 2, BRIDGE_Y + 5, Kind::Forge),
|
||||
(WALL_X - 5, BRIDGE_Y - 9, WALL_X - 2, BRIDGE_Y - 7, Kind::Library),
|
||||
(WALL_X - 5, BRIDGE_Y + 9, WALL_X - 2, BRIDGE_Y + 11, Kind::Temple),
|
||||
(6, 15, 11, 19, Kind::Lair),
|
||||
(9, 29, 14, 33, Kind::Training),
|
||||
(14, 12, 18, 16, Kind::Aerie),
|
||||
] {
|
||||
for y in y0..=y1 {
|
||||
for x in x0..=x1 {
|
||||
let t = &mut g.map.t[idx(x, y)];
|
||||
@@ -136,13 +170,47 @@ impl Game {
|
||||
let towers = vec![
|
||||
(river_mid(n) - 4, n, TowerKind::Arrow, false, 0.0),
|
||||
(river_mid(s_) - 4, s_, TowerKind::Cannon, false, 0.0),
|
||||
(RIVER_X - 9, BRIDGE_Y + 1, TowerKind::Magic, false, 0.0),
|
||||
(WALL_X + 2, BRIDGE_Y + 4, TowerKind::Magic, false, 0.0),
|
||||
(WALL_X + 2, BRIDGE_Y - 2, TowerKind::Rune, false, 0.0),
|
||||
(river_mid(n) + 4, n, TowerKind::Arrow, true, 0.0),
|
||||
(river_mid(s_) + 4, s_, TowerKind::Arrow, true, 0.0),
|
||||
(RIVER_X + 9, BRIDGE_Y + 1, TowerKind::Cannon, true, 0.0),
|
||||
(RIVER_X + 8, BRIDGE_Y - 7, TowerKind::Cannon, true, 0.0),
|
||||
];
|
||||
g.demo = Some(Demo { spawn_t: 0.0, towers, rng: Rng(seed.wrapping_mul(747796405) | 1) });
|
||||
g.gate_hp = 0.0;
|
||||
// treasure in the treasury, a mana spring by the wall
|
||||
for t in g.map.t.iter_mut().filter(|t| t.kind == Kind::Treasury) {
|
||||
t.gold = 300;
|
||||
}
|
||||
let spring = idx(WALL_X - 3, BRIDGE_Y + 1);
|
||||
g.map.t[spring].kind = Kind::ManaSpring;
|
||||
g.map.t[spring].owner = Owner::Player;
|
||||
// the hero gate on the knights' side
|
||||
let (gx, gy) = (RIVER_X + 11, BRIDGE_Y);
|
||||
for y in gy..gy + 3 {
|
||||
for x in gx..gx + 3 {
|
||||
let t = &mut g.map.t[idx(x, y)];
|
||||
t.kind = Kind::Gate;
|
||||
t.owner = Owner::Hero;
|
||||
}
|
||||
}
|
||||
g.map.gate = (gx, gy);
|
||||
// the doors, and every trap on the knights' way: on the tongue and before the wall
|
||||
use crate::traps::TrapKind as T;
|
||||
let mut traps: Vec<(usize, T, f32)> = doors.iter().map(|&(y, k)| (idx(WALL_X, y), k, 0.0)).collect();
|
||||
let m = river_mid(BRIDGE_Y + 1);
|
||||
for (x, y, k) in [
|
||||
(m - 1, BRIDGE_Y, T::Spikes),
|
||||
(m + 1, BRIDGE_Y + 2, T::Poison),
|
||||
(m, BRIDGE_Y + 1, T::Fire),
|
||||
(m - 2, BRIDGE_Y + 2, T::Frost),
|
||||
(m + 2, BRIDGE_Y, T::Mine),
|
||||
(WALL_X + 3, BRIDGE_Y + 1, T::Roller),
|
||||
(WALL_X + 4, BRIDGE_Y - 4, T::Alarm),
|
||||
] {
|
||||
traps.push((idx(x, y), k, 0.0));
|
||||
}
|
||||
g.demo = Some(Demo { spawn_t: 0.0, towers, traps, spell_t: 6.0, rng: Rng(seed.wrapping_mul(747796405) | 1) });
|
||||
g.place_all_traps_of_demo();
|
||||
g.gate_hp = GATE_HP;
|
||||
g.gate_on = false;
|
||||
g.mana = MANA_MAX;
|
||||
g.place_all_towers_of_demo();
|
||||
@@ -157,6 +225,29 @@ impl Game {
|
||||
(g, cam)
|
||||
}
|
||||
|
||||
// a trap or door of the demo on its tile (again)
|
||||
fn demo_trap(&mut self, i: usize, kind: crate::traps::TrapKind) {
|
||||
use crate::traps::*;
|
||||
if self.traps.iter().any(|t| t.tile == i) {
|
||||
return;
|
||||
}
|
||||
if kind.door() {
|
||||
self.map.t[i].kind = Kind::Door;
|
||||
}
|
||||
self.map.t[i].owner = Owner::Player;
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.traps.push(Trap { id, kind, tile: i, stage: ARMED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: kind.hp(), locked: false, carrier: 0, fired: 99.0, aim: 0.0, revealed: false, sprung: false });
|
||||
}
|
||||
|
||||
fn place_all_traps_of_demo(&mut self) {
|
||||
let Some(d) = self.demo.as_ref() else { return };
|
||||
let slots: Vec<(usize, crate::traps::TrapKind)> = d.traps.iter().map(|s| (s.0, s.1)).collect();
|
||||
for (i, k) in slots {
|
||||
self.demo_trap(i, k);
|
||||
}
|
||||
}
|
||||
|
||||
fn place_all_towers_of_demo(&mut self) {
|
||||
let Some(d) = self.demo.as_ref() else { return };
|
||||
let slots: Vec<(i32, i32, TowerKind, bool)> = d.towers.iter().map(|s| (s.0, s.1, s.2, s.3)).collect();
|
||||
@@ -207,7 +298,41 @@ impl Game {
|
||||
self.mana = MANA_MAX;
|
||||
self.update_units(dt);
|
||||
self.update_towers(dt);
|
||||
self.demo_traps(dt);
|
||||
let Some(mut d) = self.demo.take() else { return };
|
||||
// destroyed doors and wrecked traps come back too
|
||||
let mut retrap = Vec::new();
|
||||
for s in d.traps.iter_mut() {
|
||||
if self.traps.iter().any(|t| t.tile == s.0) {
|
||||
s.2 = 0.0;
|
||||
} else {
|
||||
s.2 += dt;
|
||||
if s.2 >= REBUILD {
|
||||
s.2 = 0.0;
|
||||
retrap.push((s.0, s.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
// now and then the dungeon casts a spell into the fight
|
||||
d.spell_t -= dt;
|
||||
let mut spell = None;
|
||||
if d.spell_t <= 0.0 {
|
||||
d.spell_t = 9.0 + d.rng.f() * 6.0;
|
||||
use crate::spells::Spell;
|
||||
let pick = d.rng.next() % 4;
|
||||
let knights: Vec<(f32, f32)> = self.ents.iter().filter(|e| e.alive && e.active_hero()).map(|e| (e.x, e.y)).collect();
|
||||
let mine: Vec<(f32, f32)> = self.ents.iter().filter(|e| e.alive && !e.hero && e.side == 0).map(|e| (e.x, e.y)).collect();
|
||||
let (list, sp) = match pick {
|
||||
0 => (&knights, Spell::Quake),
|
||||
1 => (&knights, Spell::Plague),
|
||||
2 => (&mine, Spell::Haste),
|
||||
_ => (&mine, Spell::Stoneskin),
|
||||
};
|
||||
if !list.is_empty() {
|
||||
let at = list[d.rng.next() as usize % list.len()];
|
||||
spell = Some((sp, at));
|
||||
}
|
||||
}
|
||||
// reinforcements: the weaker side gets the next unit
|
||||
d.spawn_t -= dt;
|
||||
let mut want = None;
|
||||
@@ -243,6 +368,16 @@ impl Game {
|
||||
if let Some(creature) = want {
|
||||
self.demo_spawn(creature);
|
||||
}
|
||||
for (i, k) in retrap {
|
||||
let blocked = self.ents.iter().any(|e| e.alive && e.tile() == i);
|
||||
if !blocked {
|
||||
self.demo_trap(i, k);
|
||||
}
|
||||
}
|
||||
if let Some((sp, (x, y))) = spell {
|
||||
self.mana = MANA_MAX;
|
||||
self.cast(sp, x, y);
|
||||
}
|
||||
for (x, y, kind, hero) in rebuild {
|
||||
// nobody may stand on the spot
|
||||
let blocked = self.ents.iter().any(|e| e.alive && e.tile() == idx(x, y));
|
||||
@@ -344,6 +479,9 @@ mod tests {
|
||||
Kind::Rock => '#',
|
||||
Kind::Earth => '%',
|
||||
Kind::Tower => 'T',
|
||||
Kind::Door => 'D',
|
||||
Kind::Gate => 'G',
|
||||
Kind::ManaSpring => 'M',
|
||||
k if g.map.t[idx(x, y)].walkable() && k != Kind::Floor => 'r',
|
||||
_ if g.map.t[idx(x, y)].walkable() => '.',
|
||||
_ => ' ',
|
||||
@@ -357,7 +495,8 @@ mod tests {
|
||||
assert_eq!(g.map.t[idx(m, BRIDGE_Y + 4)].kind, Kind::Water);
|
||||
// the tongue connects both banks
|
||||
assert!(g.map.path_to(idx(m - 7, BRIDGE_Y + 1), idx(m + 6, BRIDGE_Y + 1)).is_some(), "across the tongue");
|
||||
assert_eq!(g.towers.len(), 6);
|
||||
assert_eq!(g.towers.len(), 7);
|
||||
assert!(g.traps.len() >= 10, "doors and every trap");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user