Knight fortresses: knight towers, patrols, alarm, claimable knight ground and vaults
- fortress.rs: fortress builder (masonry walls with doors, courtyard, corner towers, vault chambers full of mixed treasure, shard chamber, gate of the order as the heart, guards, patrols) - Knight towers shoot the player's units; creatures tear down knight towers within reach - Patrols walk their round; guards and patrols that spot an enemy alarm the others nearby - Imps claim knight ground (slower, not next to fighting heroes); vaults hand over their treasure, shard chambers give the shard back; claiming alarms the knights - Knights tear down the player's bridges near their posts - Fix: units next to a tower could stop at the tile edge just out of melee reach - Tests for towers, vault looting, patrols/alarm and bridge tearing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7e84cae910
commit
e15b16cc60
7 files changed
+565
-23
No files matched your search
+176
@@ -0,0 +1,176 @@
|
||||
// Knight fortresses (campaign): an outer wall of fortress masonry with doors, a courtyard of
|
||||
// knight ground, corner towers, vault chambers full of treasure, optionally a shard chamber or
|
||||
// the gate of the order (the fortress heart), guards at their posts and patrols on their rounds.
|
||||
|
||||
use crate::game::*;
|
||||
use crate::map::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FortSpec {
|
||||
pub x: i32, // top-left corner of the outer wall
|
||||
pub y: i32,
|
||||
pub w: i32, // outer size (at least 13 x 13)
|
||||
pub h: i32,
|
||||
pub doors: Vec<usize>, // sides with a door: index into DIR4 (0 north, 1 east, 2 south, 3 west)
|
||||
pub towers: Vec<TowerKind>, // placed in the inner corners, then along the walls
|
||||
pub tower_level: u32,
|
||||
pub guards: Vec<(EK, u32)>, // kind, level
|
||||
pub patrols: Vec<Vec<(EK, u32)>>, // groups walking around the courtyard
|
||||
pub vaults: u32, // number of vault chambers
|
||||
pub vault_value: i32, // treasure per vault chamber
|
||||
pub shard: bool, // shard chamber in the middle
|
||||
pub heart: bool, // the gate of the order in the middle (map.gate)
|
||||
}
|
||||
|
||||
pub const FORT_WALL_HP: f32 = 30.0;
|
||||
|
||||
impl Game {
|
||||
fn fort_tile(&mut self, x: i32, y: i32, kind: Kind) {
|
||||
if !inb(x, y) || x < 1 || y < 1 || x >= mw() as i32 - 1 || y >= mh() as i32 - 1 {
|
||||
return;
|
||||
}
|
||||
let t = &mut self.map.t[idx(x, y)];
|
||||
t.kind = kind;
|
||||
t.owner = Owner::Hero;
|
||||
t.gold = 0;
|
||||
t.marked = false;
|
||||
t.reinforced = kind == Kind::Earth;
|
||||
t.hp = if kind == Kind::Earth { FORT_WALL_HP } else { 0.0 };
|
||||
}
|
||||
|
||||
// walls around a rectangle (inclusive), floor inside
|
||||
fn fort_box(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, inside: Kind) {
|
||||
for y in y0..=y1 {
|
||||
for x in x0..=x1 {
|
||||
let edge = x == x0 || x == x1 || y == y0 || y == y1;
|
||||
self.fort_tile(x, y, if edge { Kind::Earth } else { inside });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fort_hero(&mut self, kind: EK, level: u32, x: f32, y: f32) -> usize {
|
||||
let id = self.spawn(kind, true, x, y, level.max(1));
|
||||
self.ents.iter().position(|e| e.id == id).unwrap()
|
||||
}
|
||||
|
||||
pub fn build_fortress(&mut self, f: &FortSpec) {
|
||||
let (x0, y0, x1, y1) = (f.x, f.y, f.x + f.w - 1, f.y + f.h - 1);
|
||||
let (cx, cy) = ((x0 + x1) / 2, (y0 + y1) / 2);
|
||||
self.fort_box(x0, y0, x1, y1, Kind::Floor);
|
||||
// doors: three tiles wide in the middle of a side
|
||||
for &d in &f.doors {
|
||||
for k in -1..=1 {
|
||||
let (x, y) = match d {
|
||||
0 => (cx + k, y0),
|
||||
1 => (x1, cy + k),
|
||||
2 => (cx + k, y1),
|
||||
_ => (x0, cy + k),
|
||||
};
|
||||
self.fort_tile(x, y, Kind::Floor);
|
||||
// the way out of the door is dug free
|
||||
let (dx, dy) = DIR4[d];
|
||||
for s in 1..=2 {
|
||||
let (ox, oy) = (x + dx * s, y + dy * s);
|
||||
if let Some(t) = self.map.get(ox, oy) {
|
||||
if t.solid() && t.kind != Kind::Rock {
|
||||
let t = &mut self.map.t[idx(ox, oy)];
|
||||
t.kind = Kind::Floor;
|
||||
t.owner = Owner::None;
|
||||
t.gold = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// centre: shard chamber and/or gate of the order
|
||||
if f.heart {
|
||||
for y in cy - 1..=cy + 1 {
|
||||
for x in cx - 1..=cx + 1 {
|
||||
self.fort_tile(x, y, Kind::Gate);
|
||||
}
|
||||
}
|
||||
self.map.gate = (cx - 1, cy - 1);
|
||||
}
|
||||
if f.shard {
|
||||
// a walled chamber with one door (below the gate if both exist)
|
||||
let sy = if f.heart { cy + 3 } else { cy };
|
||||
self.fort_box(cx - 2, sy - 2, cx + 2, sy + 2, Kind::Floor);
|
||||
self.fort_tile(cx, sy + 2, Kind::Floor);
|
||||
self.fort_tile(cx, sy, Kind::Shard);
|
||||
}
|
||||
// vault chambers in the corners: 3 x 3 treasury behind a wall with a door
|
||||
let corners = [(x0 + 1, y0 + 1, 1), (x1 - 5, y0 + 1, -1), (x0 + 1, y1 - 5, 1), (x1 - 5, y1 - 5, -1)];
|
||||
for &(vx, vy, sx) in corners.iter().take(f.vaults as usize) {
|
||||
self.fort_box(vx, vy, vx + 4, vy + 4, Kind::Treasury);
|
||||
// door towards the middle of the courtyard
|
||||
self.fort_tile(if sx > 0 { vx + 4 } else { vx }, vy + 2, Kind::Floor);
|
||||
// treasure: mostly gold, some silver, rubies, now and then diamonds
|
||||
let per = (f.vault_value / 9).max(1);
|
||||
for y in vy + 1..=vy + 3 {
|
||||
for x in vx + 1..=vx + 3 {
|
||||
let r = self.rng.f();
|
||||
let res = if r < 0.08 { 3 } else if r < 0.25 { 2 } else if r < 0.45 { 1 } else { 0 };
|
||||
let t = &mut self.map.t[idx(x, y)];
|
||||
t.gold = per;
|
||||
t.res = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
// towers: inner corners of the courtyard first, then along the walls
|
||||
let mut spots = vec![(x0 + 2, y0 + 2), (x1 - 2, y0 + 2), (x0 + 2, y1 - 2), (x1 - 2, y1 - 2), (cx, y0 + 2), (cx, y1 - 2), (x0 + 2, cy), (x1 - 2, cy)];
|
||||
if f.vaults > 0 {
|
||||
// vault chambers take the corners: towers stand in front of them
|
||||
for s in spots.iter_mut().take(4) {
|
||||
s.0 += if s.0 < cx { 5 } else { -5 };
|
||||
}
|
||||
}
|
||||
for (k, &kind) in f.towers.iter().enumerate() {
|
||||
let Some(&(tx, ty)) = spots.get(k) else { break };
|
||||
if self.map.get(tx, ty).map_or(true, |t| t.kind != Kind::Floor) {
|
||||
continue;
|
||||
}
|
||||
let i = idx(tx, ty);
|
||||
self.map.t[i].kind = Kind::Tower;
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
let s = tower_stats(kind);
|
||||
let mut tw = Tower { id, kind, tile: i, level: 1, hp: s.hp, cd: 1.0, aim: 0.0, flash: 0.0, hero: true };
|
||||
while tw.level < f.tower_level.min(3) {
|
||||
let old = tw.max_hp();
|
||||
tw.level += 1;
|
||||
tw.hp += tw.max_hp() - old;
|
||||
}
|
||||
self.towers.push(tw);
|
||||
}
|
||||
// guards: posts spread over the courtyard
|
||||
let n = f.guards.len().max(1) as f32;
|
||||
for (k, &(kind, level)) in f.guards.iter().enumerate() {
|
||||
let a = k as f32 / n * std::f32::consts::TAU;
|
||||
let (rx, ry) = ((f.w as f32 * 0.5 - 3.5).max(1.0), (f.h as f32 * 0.5 - 3.5).max(1.0));
|
||||
let (mut hx, mut hy) = (cx as f32 + 0.5 + a.cos() * rx * 0.6, cy as f32 + 0.5 + a.sin() * ry * 0.6);
|
||||
if !self.map.walk(hx as i32, hy as i32) {
|
||||
(hx, hy) = (cx as f32 + 0.5, (y0 + 2) as f32 + 0.5);
|
||||
}
|
||||
let j = self.fort_hero(kind, level, hx, hy);
|
||||
let e = &mut self.ents[j];
|
||||
e.guard = true;
|
||||
e.home = Some((hx, hy));
|
||||
}
|
||||
// patrols: round through the courtyard along the inside of the walls
|
||||
let route = vec![(x0 + 2, cy), (cx, y0 + 2), (x1 - 2, cy), (cx, y1 - 2)];
|
||||
let route: Vec<(i32, i32)> = route.into_iter().filter(|&(x, y)| self.map.walk(x, y)).collect();
|
||||
for (g, group) in f.patrols.iter().enumerate() {
|
||||
if route.is_empty() {
|
||||
break;
|
||||
}
|
||||
let start = (g * 2) % route.len();
|
||||
let (sx, sy) = route[start];
|
||||
for (k, &(kind, level)) in group.iter().enumerate() {
|
||||
let j = self.fort_hero(kind, level, sx as f32 + 0.5 + (k as f32 - 0.5) * 0.4, sy as f32 + 0.5);
|
||||
let e = &mut self.ents[j];
|
||||
e.patrol = route.clone();
|
||||
e.patrol_k = (start + 1) % route.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user