Imps set up torch stands in big rooms on their own ground
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c16bc7b18
commit
c5378d0774
3 files changed
+122
-12
No files matched your search
+96
-3
@@ -662,20 +662,43 @@ pub const STEP: f32 = 1.0 / 50.0; // 50 steps a second
|
||||
// one frame never runs away into the future (a lagging tab catches up at most this much)
|
||||
const MAX_STEPS_PER_FRAME: usize = 12;
|
||||
|
||||
// torch on a wall face: wall tile + direction (index into DIR4) towards the open side
|
||||
// torch on a wall face: wall tile + direction (index into DIR4) towards the open side;
|
||||
// or a torch on a little stand in the middle of a floor tile (stand: wall = that tile)
|
||||
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Torch {
|
||||
pub wall: usize,
|
||||
pub dir: usize,
|
||||
#[serde(default)]
|
||||
pub stand: bool,
|
||||
}
|
||||
|
||||
// rooms where imps may set up a torch stand (no beds, hoards, mushrooms or crates in the way)
|
||||
pub fn stand_ground(k: Kind) -> bool {
|
||||
matches!(k, Kind::Floor | Kind::Training | Kind::Library | Kind::Torture | Kind::Temple | Kind::Prison | Kind::Graveyard | Kind::GuardPost | Kind::Arena | Kind::Altar)
|
||||
}
|
||||
|
||||
// a torch stand goes where no torch shines closer than this (tiles)
|
||||
pub const STAND_GAP: f32 = 4.5;
|
||||
|
||||
impl Torch {
|
||||
// world position of the flame (x, z) on the ground plane
|
||||
pub fn pos(&self) -> (f32, f32) {
|
||||
let (x, y) = xy(self.wall);
|
||||
if self.stand {
|
||||
return (x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
let (dx, dy) = DIR4[self.dir];
|
||||
(x as f32 + 0.5 + dx as f32 * 0.58, y as f32 + 0.5 + dy as f32 * 0.58)
|
||||
}
|
||||
|
||||
// height of the bottom of the flame
|
||||
pub fn flame_h(&self) -> f32 {
|
||||
if self.stand {
|
||||
0.62
|
||||
} else {
|
||||
0.72
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const N_ROOMS: usize = 14;
|
||||
@@ -890,7 +913,7 @@ impl Game {
|
||||
pub fn place_all_torches(&mut self) {
|
||||
// torches on walls that are gone (rivers, fortresses) are removed
|
||||
let map = &self.map;
|
||||
self.torches.retain(|t| map.t[t.wall].solid());
|
||||
self.torches.retain(|t| if t.stand { stand_ground(map.t[t.wall].kind) } else { map.t[t.wall].solid() });
|
||||
for i in 0..n() {
|
||||
if self.map.t[i].owner == self.me {
|
||||
self.place_torches(i);
|
||||
@@ -998,7 +1021,7 @@ impl Game {
|
||||
continue;
|
||||
}
|
||||
// the direction from the wall to the floor is the opposite one
|
||||
let t = Torch { wall: idx(wx, wy), dir: (d + 2) % 4 };
|
||||
let t = Torch { wall: idx(wx, wy), dir: (d + 2) % 4, stand: false };
|
||||
let (px, pz) = t.pos();
|
||||
let near = self.torches.iter().any(|o| {
|
||||
let (ox, oz) = o.pos();
|
||||
@@ -1008,6 +1031,26 @@ impl Game {
|
||||
self.torches.push(t);
|
||||
}
|
||||
}
|
||||
// a big room the wall torches do not light: a torch on a stand in the middle of it,
|
||||
// on the master's own ground only (never neutral or foreign ground) and not next to a wall
|
||||
let tile = self.map.t[i];
|
||||
let open_around = (-1..=1).all(|dy| (-1..=1).all(|dx| self.map.get(x + dx, y + dy).map_or(false, |t| !t.solid())));
|
||||
if tile.owner == self.me && stand_ground(tile.kind) && open_around {
|
||||
let (px, pz) = (x as f32 + 0.5, y as f32 + 0.5);
|
||||
let near = self.torches.iter().any(|o| {
|
||||
let (ox, oz) = o.pos();
|
||||
(ox - px).powi(2) + (oz - pz).powi(2) < STAND_GAP * STAND_GAP
|
||||
});
|
||||
if !near {
|
||||
self.torches.push(Torch { wall: i, dir: 0, stand: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// torch stands go when their ground is lost or becomes a room they do not fit in
|
||||
fn tidy_torch_stands(&mut self) {
|
||||
let map = &self.map;
|
||||
self.torches.retain(|t| !t.stand || (stand_ground(map.t[t.wall].kind) && !matches!(map.t[t.wall].owner, Owner::None | Owner::Hero)));
|
||||
}
|
||||
|
||||
pub(crate) fn hint(&mut self, slot: usize, key: &'static str) {
|
||||
@@ -1563,6 +1606,7 @@ impl Game {
|
||||
// every two seconds: corners between reinforced walls are closed
|
||||
if (self.time / 2.0).floor() != ((self.time - dt) / 2.0).floor() {
|
||||
self.reinforce_corners();
|
||||
self.tidy_torch_stands();
|
||||
}
|
||||
self.update_forge(dt);
|
||||
self.update_research();
|
||||
@@ -5940,6 +5984,55 @@ mod tests {
|
||||
assert!(best <= 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imps_set_up_torch_stands_in_big_rooms_on_their_own_ground() {
|
||||
let mut g = Game::new(5, 96, 96);
|
||||
let (cx, cy) = g.map.core;
|
||||
// a big hall of own ground east of the core, and as big a neutral cave south of it
|
||||
let hall = (cx + 8, cy - 7, cx + 22, cy + 7);
|
||||
let cave = (cx - 7, cy + 12, cx + 7, cy + 26);
|
||||
for (x0, y0, x1, y1, own) in [(hall.0, hall.1, hall.2, hall.3, true), (cave.0, cave.1, cave.2, cave.3, false)] {
|
||||
for y in y0..=y1 {
|
||||
for x in x0..=x1 {
|
||||
let t = &mut g.map.t[idx(x, y)];
|
||||
t.kind = Kind::Floor;
|
||||
t.owner = if own { g.me } else { Owner::None };
|
||||
}
|
||||
}
|
||||
}
|
||||
g.torches.clear();
|
||||
g.place_all_torches();
|
||||
let inside = |t: &Torch, r: (i32, i32, i32, i32)| {
|
||||
let (x, y) = xy(t.wall);
|
||||
x >= r.0 && x <= r.2 && y >= r.1 && y <= r.3
|
||||
};
|
||||
let stands: Vec<Torch> = g.torches.iter().copied().filter(|t| t.stand).collect();
|
||||
assert!(stands.iter().any(|t| inside(t, hall)), "the big hall gets torch stands");
|
||||
assert!(!stands.iter().any(|t| inside(t, cave)), "never on neutral ground");
|
||||
for t in &stands {
|
||||
assert_eq!(g.map.t[t.wall].owner, g.me, "only on own ground");
|
||||
let (x, y) = xy(t.wall);
|
||||
assert!((-1..=1).all(|dy| (-1..=1).all(|dx| !g.map.t[idx(x + dx, y + dy)].solid())), "not next to a wall");
|
||||
}
|
||||
// every tile of the hall's middle is within reach of some torch
|
||||
for y in hall.1 + 2..=hall.3 - 2 {
|
||||
for x in hall.0 + 2..=hall.2 - 2 {
|
||||
let lit = g.torches.iter().any(|t| {
|
||||
let (px, pz) = t.pos();
|
||||
(px - x as f32 - 0.5).powi(2) + (pz - y as f32 - 0.5).powi(2) <= (STAND_GAP + 1.0).powi(2)
|
||||
});
|
||||
assert!(lit, "({}, {}) lies in the dark", x, y);
|
||||
}
|
||||
}
|
||||
// ground lost: the stand goes
|
||||
let s0 = stands.iter().find(|t| inside(t, hall)).unwrap().wall;
|
||||
g.map.t[s0].owner = Owner::Hero;
|
||||
for _ in 0..60 {
|
||||
g.update(0.05);
|
||||
}
|
||||
assert!(!g.torches.iter().any(|t| t.stand && t.wall == s0), "a stand on lost ground is removed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flyers_take_every_way_across_rivers() {
|
||||
let (mut g, rx) = river_game(false);
|
||||
|
||||
Reference in new issue
Block a user