diff options
Diffstat (limited to 'src/town/interior.rs')
| -rw-r--r-- | src/town/interior.rs | 1112 |
1 files changed, 1112 insertions, 0 deletions
diff --git a/src/town/interior.rs b/src/town/interior.rs new file mode 100644 index 0000000..4e09333 --- /dev/null +++ b/src/town/interior.rs @@ -0,0 +1,1112 @@ +//! Inside a building. +//! +//! Inside is drawn at a much closer scale than the street: a house three +//! rows deep outside is eleven rows deep inside. You come in by a door on +//! the same side as the one you bumped outside, at the same place along +//! the wall. +//! +//! Rooms come from splitting the floor with walls, each wall with a +//! doorway, so every room can be reached. Most buildings have a front room +//! as wide as the building with smaller rooms behind it: a shop's counter, +//! a tavern's taproom, a temple's nave, a palace's throne room. Houses are +//! split any which way. Each room is furnished for what it's for, with a +//! check after every piece that nothing has been cut off, and then the +//! people go in. +//! +//! "Back" is the side away from the door, wherever that is. Rooms are +//! furnished in a frame turned to match: `u` runs along the back wall and +//! `v` away from it, so a counter or an altar always stands at the back. + +use rand::RngExt; +use rand::seq::{IndexedRandom, SliceRandom}; + +use super::words; +use super::*; +use crate::seed::Rng; + +/// Furniture. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Thing { + Table, + Chair, + Bed, + Hearth, + Stove, + Shelf, + Chest, + Crate, + Barrel, + Altar, + Pew, + Pillar, + Statue, + Throne, + Plant, + /// Hot water, in a bathhouse. + Bath, + Anvil, + Forge, + Loom, + Workbench, + /// Something to sit on, in a changing room or a steam room. + Bench, + /// Weapons, in a guardroom. + Rack, + Lamp, + /// Pieces of ship, in a shipyard. + Engine, +} + +impl Thing { + /// Seats can be walked over; people sit on them. + pub fn walkable(self) -> bool { + matches!(self, Thing::Chair | Thing::Pew | Thing::Bench | Thing::Throne) + } +} + +/// What a room is for. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Purpose { + Living, + Bedroom, + Kitchen, + Store, + /// A room of one's own, in a tenement. + Lodging, + Taproom, + Parlour, + Cellar, + /// A shop's front room. + Counter, + Ward, + Stacks, + Assay, + Yard, + /// Where an armourer makes and mends. + Smithy, + Hall, + Dining, + Library, + Bedchamber, + Gallery, + Throne, + Council, + Treasury, + Nave, + Vestry, + Cell, + Shrine, + Workroom, + Storehouse, + Office, + Changing, + Baths, + Steam, + Guardroom, + Bunks, +} + +struct Room { + rect: Rect, + purpose: Purpose, +} + +pub struct Interior { + pub building: usize, + pub culture: Culture, + pub kind: Kind, + pub site: Site, + rooms: Vec<Room>, + /// Which room each cell is in, `NO_ROOM` for walls and doorways. + room_of: Vec<u8>, + /// Where you stand when you come in, just inside the door. + pub start: Pos, + pub exit: Pos, + /// The shop whose counter this is. + pub shop: Option<usize>, +} + +const NO_ROOM: u8 = u8::MAX; + +impl Interior { + pub fn purpose_at(&self, p: Pos) -> Option<Purpose> { + if !self.site.contains(p) { + return None; + } + self.rooms.get(usize::from(self.room_of[self.site.index(p)])).map(|r| r.purpose) + } + + pub fn room_count(&self) -> usize { + self.rooms.len() + } + + /// The shop, if `p` is its counter or its keeper. + pub fn shop_at(&self, p: Pos) -> Option<usize> { + let shop = self.shop?; + let keeper = self.site.person_at(p).map(|i| self.site.people[i].role); + (self.site.tile(p) == Tile::Counter || keeper == Some(Role::Keeper(shop))).then_some(shop) + } +} + +/// A room, turned so its back wall is at `v = -1`. +#[derive(Clone, Copy)] +struct Frame { + r: Rect, + back: Side, +} + +impl Frame { + fn len_u(self) -> i32 { + if matches!(self.back, Side::North | Side::South) { + self.r.width() + } else { + self.r.height() + } + } + + fn len_v(self) -> i32 { + if matches!(self.back, Side::North | Side::South) { + self.r.height() + } else { + self.r.width() + } + } + + fn at(self, u: i32, v: i32) -> Pos { + let r = self.r; + match self.back { + Side::North => Pos::new(r.left + u, r.top + v), + Side::South => Pos::new(r.left + u, r.bottom - v), + Side::West => Pos::new(r.left + v, r.top + u), + Side::East => Pos::new(r.right - v, r.top + u), + } + } +} + +enum Layout { + /// Split any which way, into this many rooms or so. + Split(usize, usize), + /// A front room as wide as the building, `share` of its depth, with + /// this many rooms side by side behind it. + Hall(f64, usize, usize), + Single, +} + +/// How a building of this kind is laid out: the plan, what the front room +/// is for, and what the rooms behind it are for, in order. If there are +/// more rooms than purposes, the last purpose repeats. +fn layout(kind: Kind) -> (Layout, Purpose, &'static [Purpose]) { + use Purpose::*; + match kind { + Kind::House => (Layout::Split(1, 4), Living, &[Bedroom, Kitchen, Bedroom, Store, Bedroom]), + Kind::Tenement => (Layout::Split(4, 9), Kitchen, &[Lodging]), + Kind::Manor => ( + Layout::Hall(0.45, 3, 6), + Hall, + &[Dining, Library, Bedchamber, Gallery, Kitchen, Bedchamber, Store], + ), + Kind::Palace => ( + Layout::Hall(0.55, 4, 8), + Throne, + &[Council, Treasury, Library, Bedchamber, Dining, Gallery, Kitchen, Guardroom], + ), + Kind::Temple => (Layout::Hall(0.72, 1, 3), Nave, &[Vestry, Cell, Cell]), + Kind::Shrine => (Layout::Single, Shrine, &[]), + Kind::Workshop => (Layout::Hall(0.62, 1, 2), Workroom, &[Store, Bedroom]), + Kind::Warehouse => (Layout::Hall(0.8, 1, 1), Storehouse, &[Office]), + Kind::Bathhouse => (Layout::Hall(0.32, 1, 2), Changing, &[Baths, Steam]), + Kind::Guardhouse => (Layout::Split(2, 3), Guardroom, &[Bunks, Store]), + Kind::Shop(ShopKind::Broker) => (Layout::Hall(0.55, 1, 2), Counter, &[Assay, Store]), + Kind::Shop(ShopKind::Doctor) => (Layout::Hall(0.5, 1, 2), Counter, &[Ward, Store]), + Kind::Shop(ShopKind::Scholar) => (Layout::Hall(0.5, 1, 2), Counter, &[Stacks]), + Kind::Shop(ShopKind::Tavern) => { + (Layout::Hall(0.65, 1, 3), Taproom, &[Parlour, Cellar, Kitchen]) + } + Kind::Shop(ShopKind::Shipyard) => (Layout::Hall(0.45, 1, 2), Counter, &[Yard, Store]), + Kind::Shop(ShopKind::Armoury) => (Layout::Hall(0.5, 1, 2), Counter, &[Smithy, Store]), + } +} + +struct Builder { + culture: Culture, + kind: Kind, + rng: Rng, + site: Site, + rooms: Vec<Room>, + start: Pos, + exit: Pos, + /// Which side the back wall is on. + back: Side, + shop: Option<usize>, + keeper: Option<String>, + /// How many cells you can walk to from the door, to check nothing gets + /// cut off as the furniture goes in. + reachable: usize, +} + +pub(super) fn build(t: &Town, b: usize) -> Interior { + let building = &t.buildings[b]; + let kind = building.kind; + let mut rng = seed::rng(seed::derive(t.seed, "interior", b as u64)); + let scale = if matches!(kind, Kind::Palace | Kind::Temple | Kind::Manor) { 3.6 } else { 3.4 }; + let grow = |n: i32, lo: i32, hi: i32| ((f64::from(n) * scale).round() as i32 + 1).clamp(lo, hi); + let shopfront = matches!(kind, Kind::Shop(_)); + let width = grow(building.width(), if shopfront { 17 } else { 13 }, 77); + let height = grow(building.height(), if shopfront { 11 } else { 9 }, 29); + let width = width + rng.random_range(0..=2); + + // The door in, at the same place along the wall as outside. + let along = |at: i32, lo: i32, hi: i32, len: i32| { + let k = if hi > lo { f64::from(at - lo) / f64::from(hi - lo) } else { 0.5 }; + ((1.0 + k * f64::from(len - 3)).round() as i32).clamp(2, len - 3) + }; + let (bx, by) = (building.door.x, building.door.y); + let exit = match building.facing { + Side::North => Pos::new(along(bx, building.left, building.right, width), 0), + Side::South => Pos::new(along(bx, building.left, building.right, width), height - 1), + Side::West => Pos::new(0, along(by, building.top, building.bottom, height)), + Side::East => Pos::new(width - 1, along(by, building.top, building.bottom, height)), + }; + let (dx, dy) = building.facing.step(); + let start = exit.offset(-dx, -dy); + + let mut site = Site::new(width, height, Tile::Wall, Tile::Wall, false); + site.fill(Rect::new(1, 1, width - 2, height - 2), Tile::Floor); + site.set(exit, Tile::Exit); + let shop = building.shop; + let mut builder = Builder { + culture: t.culture, + kind, + rng, + site, + rooms: Vec::new(), + start, + exit, + back: building.facing.opposite(), + shop, + keeper: shop.map(|s| t.shops[s].keeper.clone()), + reachable: 0, + }; + builder.divide(); + builder.reachable = builder.count_reachable(); + for room in 0..builder.rooms.len() { + builder.furnish(room); + } + builder.residents(); + + let Builder { site, rooms, .. } = builder; + let mut room_of = vec![NO_ROOM; (width * height) as usize]; + for (i, room) in rooms.iter().enumerate() { + for p in room.rect.cells() { + room_of[site.index(p)] = i as u8; + } + } + Interior { building: b, culture: t.culture, kind, site, rooms, room_of, start, exit, shop } +} + +impl Builder { + fn tile(&self, p: Pos) -> Tile { + self.site.tile(p) + } + + fn is_door(&self, p: Pos) -> bool { + matches!(self.tile(p), Tile::Doorway | Tile::Exit) + } + + /// Whether a wall across `r` at `at` would block a doorway that opens + /// onto the end of it. + fn can_wall(&self, r: Rect, upright: bool, at: i32) -> bool { + let ends = if upright { + [Pos::new(at, r.top - 1), Pos::new(at, r.bottom + 1)] + } else { + [Pos::new(r.left - 1, at), Pos::new(r.right + 1, at)] + }; + !ends.iter().any(|&p| self.is_door(p)) + } + + /// Builds a wall across `r`, upright (a column) or not (a row), with a + /// doorway in it somewhere in `doors`. Returns the rooms either side. + fn wall( + &mut self, + r: Rect, + upright: bool, + at: i32, + doors: std::ops::RangeInclusive<i32>, + ) -> (Rect, Rect) { + let cells: Vec<Pos> = if upright { + (r.top..=r.bottom).map(|y| Pos::new(at, y)).collect() + } else { + (r.left..=r.right).map(|x| Pos::new(x, at)).collect() + }; + for &p in &cells { + self.site.set(p, Tile::Wall); + } + let options: Vec<Pos> = cells + .iter() + .copied() + .filter(|p| { + let k = if upright { p.y } else { p.x }; + doors.contains(&k) + }) + .collect(); + let door = options.choose(&mut self.rng).or(cells.get(cells.len() / 2)).copied(); + if let Some(door) = door { + self.site.set(door, Tile::Doorway); + } + if upright { + ( + Rect::new(r.left, r.top, at - 1, r.bottom), + Rect::new(at + 1, r.top, r.right, r.bottom), + ) + } else { + ( + Rect::new(r.left, r.top, r.right, at - 1), + Rect::new(r.left, at + 1, r.right, r.bottom), + ) + } + } + + /// Splits the floor into rooms. + fn divide(&mut self) { + let floor = Rect::new(1, 1, self.site.width - 2, self.site.height - 2); + let (plan, front, behind) = layout(self.kind); + let mut rects = Vec::new(); + match plan { + Layout::Single => rects.push(floor), + Layout::Split(lo, hi) => { + let area = floor.area() as usize; + let most = (area / 40).max(1); + let target = self.rng.random_range(lo..=hi).min(most).max(lo.min(most)); + self.split(floor, target, &mut rects); + } + Layout::Hall(share, lo, hi) => self.hall(floor, share, (lo, hi), &mut rects), + } + // The front room is the one you walk into. + let first = rects.iter().position(|r| r.contains(self.start)).unwrap_or(0); + let front_rect = rects.remove(first); + self.rooms.push(Room { rect: front_rect, purpose: front }); + for (i, rect) in rects.into_iter().enumerate() { + let purpose = behind.get(i).or(behind.last()).copied().unwrap_or(front); + self.rooms.push(Room { rect, purpose }); + } + } + + fn split(&mut self, r: Rect, rooms: usize, out: &mut Vec<Rect>) { + let (min_w, min_h) = (4, 3); + if rooms <= 1 { + out.push(r); + return; + } + let wide = + f64::from(r.width()) > 2.0 * f64::from(r.height()) * self.rng.random_range(0.7..1.4); + for upright in [wide, !wide] { + let (lo, hi) = if upright { + (r.left + min_w, r.right - min_w) + } else { + (r.top + min_h, r.bottom - min_h) + }; + let options: Vec<i32> = (lo..=hi).filter(|&at| self.can_wall(r, upright, at)).collect(); + let Some(&at) = options.choose(&mut self.rng) else { continue }; + let span = if upright { r.top + 1..=r.bottom - 1 } else { r.left + 1..=r.right - 1 }; + let (a, b) = self.wall(r, upright, at, span); + // Share the rooms out by size. + let share = a.area() as f64 / (a.area() + b.area()).max(1) as f64; + let first = ((rooms as f64 * share).round() as usize).clamp(1, rooms - 1); + self.split(a, first, out); + self.split(b, rooms - first, out); + return; + } + out.push(r); + } + + fn hall(&mut self, floor: Rect, share: f64, (lo, hi): (usize, usize), out: &mut Vec<Rect>) { + let f = Frame { r: floor, back: self.back }; + let (len_u, len_v) = (f.len_u(), f.len_v()); + let upright = matches!(self.back, Side::West | Side::East); + // The back rooms take `v` from 0 to `k - 1`, then comes the wall, + // then the front room, at least four deep for a counter. + let least = if upright { 4 } else { 2 }; + if len_v < least + 5 { + out.push(floor); + return; + } + let k = ((f64::from(len_v) * (1.0 - share)).round() as i32).clamp(least, len_v - 5); + let at = if upright { f.at(0, k).x } else { f.at(0, k).y }; + // Temples and palaces keep the middle of the wall for the altar or + // throne, so their doorway is off to one side. + let off_centre = matches!(self.kind, Kind::Temple | Kind::Palace); + let span = if upright { + floor.top + 1..=floor.bottom - 1 + } else { + floor.left + 1..=floor.right - 1 + }; + let span = if off_centre { + let (a, b) = (*span.start(), *span.end()); + let quarter = ((b - a) / 5).max(1); + if self.rng.random_bool(0.5) { a..=a + quarter } else { b - quarter..=b } + } else { + span + }; + let (one, two) = self.wall(floor, upright, at, span); + let (back_rect, front_rect) = + if one.contains(f.at(0, 0)) { (one, two) } else { (two, one) }; + out.push(front_rect); + // Rooms side by side behind: split across `u`. + let min_u = if upright { 3 } else { 5 }; + let most = (len_u / (min_u + 1)).max(1) as usize; + let n = self.rng.random_range(lo..=hi).min(most); + let mut rest = back_rect; + for i in (1..n).rev() { + // Cut off one room from the far end of `rest`, sized to leave + // the others about as big. + let (from, to) = + if upright { (rest.top, rest.bottom) } else { (rest.left, rest.right) }; + let size = (to - from + 1) / (i as i32 + 1); + let ideal = to - size; + let options: Vec<i32> = (ideal - 2..=ideal + 2) + .filter(|&a| a - from >= min_u && to - a >= min_u) + .filter(|&a| self.can_wall(rest, !upright, a)) + .collect(); + let Some(&a) = options.choose(&mut self.rng) else { break }; + let span = if upright { rest.left..=rest.right } else { rest.top..=rest.bottom }; + let (near, far) = self.wall(rest, !upright, a, span); + out.push(far); + rest = near; + } + out.push(rest); + } + + fn count_reachable(&self) -> usize { + self.site.reachable(self.start).iter().filter(|&&seen| seen).count() + } + + /// Puts a piece of furniture on a bare floor cell, unless it would + /// crowd a doorway or cut off part of the building. + fn put(&mut self, p: Pos, thing: Thing) -> bool { + if self.tile(p) != Tile::Floor || p.neighbours().any(|n| self.is_door(n)) || p == self.start + { + return false; + } + self.site.set(p, Tile::Furniture(thing)); + if thing.walkable() { + return true; + } + let now = self.count_reachable(); + if now + 1 == self.reachable { + self.reachable = now; + true + } else { + self.site.set(p, Tile::Floor); + false + } + } + + fn frame(&self, room: usize) -> Frame { + Frame { r: self.rooms[room].rect, back: self.back } + } + + fn floor_of(&self, room: usize) -> Vec<Pos> { + self.rooms[room].rect.cells().filter(|&p| self.tile(p) == Tile::Floor).collect() + } + + /// Bare floor against the walls, in a random order. + fn by_wall(&mut self, room: usize) -> Vec<Pos> { + let mut cells: Vec<Pos> = self + .floor_of(room) + .into_iter() + .filter(|&p| { + [(0, -1), (0, 1), (-1, 0), (1, 0)] + .iter() + .any(|&(dx, dy)| self.tile(p.offset(dx, dy)) == Tile::Wall) + }) + .collect(); + cells.shuffle(&mut self.rng); + cells + } + + /// Bare floor out in the room, away from the walls, in a random order. + fn middle(&mut self, room: usize) -> Vec<Pos> { + let mut cells: Vec<Pos> = self + .floor_of(room) + .into_iter() + .filter(|&p| p.neighbours().all(|n| self.tile(n) != Tile::Wall)) + .collect(); + cells.shuffle(&mut self.rng); + cells + } + + fn scatter(&mut self, cells: Vec<Pos>, thing: Thing, n: usize) -> usize { + let mut placed = 0; + for p in cells { + if placed == n { + break; + } + if self.put(p, thing) { + placed += 1; + } + } + placed + } + + fn against(&mut self, room: usize, thing: Thing, lo: usize, hi: usize) { + let n = self.rng.random_range(lo..=hi); + let cells = self.by_wall(room); + self.scatter(cells, thing, n); + } + + fn out_in(&mut self, room: usize, thing: Thing, lo: usize, hi: usize) { + let n = self.rng.random_range(lo..=hi); + let cells = self.middle(room); + self.scatter(cells, thing, n); + } + + fn rug(&mut self, room: usize, chance: f64) { + if !self.rng.random_bool(chance) { + return; + } + let r = self.rooms[room].rect; + if r.width() < 6 || r.height() < 4 { + return; + } + let inner = Rect::new(r.left + 2, r.top + 1, r.right - 2, r.bottom - 1); + for p in inner.cells() { + if self.tile(p) == Tile::Floor { + self.site.set(p, Tile::Rug); + } + } + } + + /// A table a few cells long out in the room, chairs round it. + fn table(&mut self, room: usize, seats: usize) -> Vec<Pos> { + let cells = self.middle(room); + for c in cells.into_iter().take(6) { + let len = self.rng.random_range(1..=3); + if !(0..len).all(|i| self.tile(c.offset(i, 0)) == Tile::Floor) { + continue; + } + let mut placed = 0; + for i in 0..len { + if self.put(c.offset(i, 0), Thing::Table) { + placed += 1; + } + } + if placed == 0 { + continue; + } + let mut around: Vec<Pos> = (0..placed) + .flat_map(|i| [c.offset(i, -1), c.offset(i, 1)]) + .chain([c.offset(-1, 0), c.offset(placed, 0)]) + .collect(); + around.shuffle(&mut self.rng); + let mut chairs = Vec::new(); + for p in around.into_iter().take(seats.max(2)) { + if self.put(p, Thing::Chair) { + chairs.push(p); + } + } + return chairs; + } + Vec::new() + } + + /// A long table down the middle of the room, chairs either side. + fn long_table(&mut self, room: usize) -> Vec<Pos> { + let r = self.rooms[room].rect; + let c = r.centre(); + let mut chairs = Vec::new(); + if r.width() < 7 || r.height() < 3 { + return chairs; + } + for x in r.left + 2..=r.right - 2 { + if self.put(Pos::new(x, c.y), Thing::Table) { + for dy in [-1, 1] { + if self.put(Pos::new(x, c.y + dy), Thing::Chair) { + chairs.push(Pos::new(x, c.y + dy)); + } + } + } + } + chairs + } + + /// Rows of something across the room, every `every` rows back from the + /// back wall, leaving aisles at the ends and, if `aisle`, one down the + /// middle. + fn rows(&mut self, room: usize, thing: Thing, first: i32, every: i32, aisle: bool) -> Vec<Pos> { + let f = self.frame(room); + let (len_u, len_v) = (f.len_u(), f.len_v()); + let mid = len_u / 2; + let mut placed = Vec::new(); + let mut v = first; + while v < len_v - 2 { + for u in 1..len_u - 1 { + if aisle && (u - mid).abs() <= 1 { + continue; + } + let p = f.at(u, v); + if self.put(p, thing) { + placed.push(p); + } + } + v += every; + } + placed + } + + /// A runner from the front of the room to the back, down the middle. + fn runner(&mut self, room: usize) { + let f = self.frame(room); + let mid = f.len_u() / 2; + for v in 0..f.len_v() { + for u in [mid - 1, mid, mid + 1] { + let p = f.at(u, v); + if self.tile(p) == Tile::Floor { + self.site.set(p, Tile::Rug); + } + } + } + } + + /// The keeper's corner at the back of the room: shelves against the + /// back wall, a counter in front of them, the keeper between, and the + /// counter turned back to the wall at either end so nobody walks round + /// it. Keeps clear of doorways in the back wall. Returns where the + /// keeper stands. + fn nook(&mut self, room: usize, share: f64) -> Option<Pos> { + let f = self.frame(room); + let (len_u, len_v) = (f.len_u(), f.len_v()); + if len_v < 4 || len_u < 5 { + return None; + } + // Doorways in the back wall, and the side walls by the back. + let blocked: Vec<i32> = + (0..len_u).filter(|&u| self.is_door(f.at(u, -1)) || self.is_door(f.at(u, 0))).collect(); + let side_doors = (0..3).any(|v| self.is_door(f.at(-1, v))) + || (0..3).any(|v| self.is_door(f.at(len_u, v))); + // The longest run of the back wall with no doorway within a cell. + let mut best = (0, -1); + let mut start = 0; + for u in 0..=len_u { + let clear = u < len_u && !blocked.iter().any(|&b| (b - u).abs() <= 1); + if !clear { + if u - 1 - start > best.1 - best.0 { + best = (start, u - 1); + } + start = u + 1; + } + } + let (lo, hi) = best; + // Room for a cap at each end that isn't against a side wall. + let lo = if lo == 0 && !side_doors { 0 } else { lo + 1 }; + let hi = if hi == len_u - 1 && !side_doors { hi } else { hi - 1 }; + let room_for = hi - lo + 1; + if room_for < 3 { + return None; + } + let want = ((f64::from(len_u) * share).round() as i32).clamp(3, room_for); + let a = self.rng.random_range(lo..=hi - want + 1); + let b = a + want - 1; + let mid = (a + b) / 2; + for u in a - 1..=b + 1 { + if u >= 0 && u < len_u { + self.site.set(f.at(u, 1), Tile::Counter); + } + } + for u in [a - 1, b + 1] { + if u >= 0 && u < len_u { + self.site.set(f.at(u, 0), Tile::Counter); + } + } + for u in a..=b { + self.site.set(f.at(u, 0), if u == mid { Tile::Floor } else { Tile::Shelf }); + } + self.reachable = self.count_reachable(); + Some(f.at(mid, 0)) + } + + /// A free floor cell in the room that you can walk to. + fn spot(&mut self, room: usize, seen: &[bool]) -> Option<Pos> { + let mut cells: Vec<Pos> = self.rooms[room] + .rect + .cells() + .filter(|&p| { + matches!(self.tile(p), Tile::Floor | Tile::Rug) + && seen[self.site.index(p)] + && self.site.person_at(p).is_none() + && p != self.start + && !p.neighbours().any(|n| n == self.exit) + }) + .collect(); + cells.shuffle(&mut self.rng); + cells.first().copied() + } + + fn add(&mut self, pos: Pos, role: Role, stays: bool) { + let named = matches!( + role, + Role::Resident | Role::Ruler | Role::Priest | Role::Worker | Role::Courtier + ); + let name = match role { + Role::Keeper(_) => self.keeper.clone(), + _ if named => Some(self.culture.person(&mut self.rng)), + _ => None, + }; + let says = *words::lines(role, self.culture).choose(&mut self.rng).unwrap_or(&"..."); + self.site.people.push(Person { pos, role, name, says, stays }); + } + + /// Up to `n` people wandering the room. + fn crowd(&mut self, room: usize, role: Role, n: std::ops::RangeInclusive<usize>) { + let n = self.rng.random_range(n); + let seen = self.site.reachable(self.start); + for _ in 0..n { + if let Some(p) = self.spot(room, &seen) { + self.add(p, role, false); + } + } + } + + /// Up to `n` people sitting in these seats, staying put. + fn seat(&mut self, seats: &[Pos], role: Role, n: std::ops::RangeInclusive<usize>) { + let n = self.rng.random_range(n); + let mut seats: Vec<Pos> = + seats.iter().copied().filter(|&p| self.site.person_at(p).is_none()).collect(); + seats.shuffle(&mut self.rng); + for p in seats.into_iter().take(n) { + self.add(p, role, true); + } + } + + /// Puts `thing` at the middle of the back wall, and returns the cell in + /// front of it. + fn at_back(&mut self, room: usize, thing: Thing) -> Option<Pos> { + let f = self.frame(room); + let mid = f.len_u() / 2; + for u in [mid, mid - 1, mid + 1, mid - 2, mid + 2] { + let p = f.at(u, 0); + if self.put(p, thing) { + return Some(f.at(u, 1)); + } + } + None + } + + fn furnish(&mut self, room: usize) { + use Purpose::*; + use Thing::*; + let purpose = self.rooms[room].purpose; + let area = self.rooms[room].rect.area() as usize; + let culture = self.culture; + match purpose { + Living => { + self.against(room, Hearth, 1, 1); + self.table(room, 4); + self.against(room, Shelf, 1, 2); + self.against(room, Chest, 0, 1); + self.out_in(room, Plant, 0, 1); + self.rug(room, 0.3); + } + Bedroom => { + self.against(room, Bed, 1, 2); + self.against(room, Chest, 0, 1); + self.rug(room, 0.3); + } + Lodging => { + self.against(room, Bed, 1, 1); + self.against(room, Chest, 0, 1); + self.crowd(room, Role::Resident, 0..=1); + } + Kitchen => { + self.against(room, Stove, 1, 1); + self.against(room, Barrel, 1, 3); + self.against(room, Shelf, 0, 1); + self.table(room, 2); + if self.kind == Kind::Tenement { + self.crowd(room, Role::Resident, 1..=2); + } + } + Store => { + self.against(room, Crate, 2, 5); + self.against(room, Barrel, 1, 3); + } + Taproom => { + if let Some(at) = self.nook(room, 0.45) { + self.add(at, Role::Keeper(self.shop.unwrap_or(0)), true); + } + self.against(room, Hearth, 1, 1); + let mut seats = Vec::new(); + for _ in 0..(area / 28).clamp(2, 8) { + seats.extend(self.table(room, 4)); + } + let n = self.rng.random_range(3..=(seats.len().max(3))); + self.seat(&seats, Role::Patron, n..=n); + self.crowd(room, Role::Patron, 0..=2); + } + Parlour => { + let mut seats = self.table(room, 4); + if area > 40 { + seats.extend(self.table(room, 4)); + } + self.seat(&seats, Role::Patron, 1..=3); + self.against(room, Shelf, 0, 1); + } + Cellar => { + self.rows(room, Barrel, 1, 2, false); + self.against(room, Crate, 1, 3); + } + Counter => { + if let Some(at) = self.nook(room, 0.75) { + self.add(at, Role::Keeper(self.shop.unwrap_or(0)), true); + } + match self.kind { + Kind::Shop(ShopKind::Doctor) => { + let cells = self.by_wall(room); + let seats: Vec<Pos> = + cells.into_iter().filter(|&p| self.put(p, Chair)).take(3).collect(); + self.seat(&seats, Role::Patient, 0..=1); + self.out_in(room, Plant, 0, 1); + } + Kind::Shop(ShopKind::Scholar) => { + self.against(room, Shelf, 2, 5); + self.out_in(room, Lamp, 1, 1); + } + Kind::Shop(ShopKind::Broker) => self.against(room, Crate, 2, 4), + Kind::Shop(ShopKind::Shipyard) => { + self.out_in(room, Engine, 1, 2); + self.against(room, Crate, 1, 3); + } + Kind::Shop(ShopKind::Armoury) => self.against(room, Rack, 2, 5), + _ => {} + } + } + Ward => { + let n = (self.rooms[room].rect.width() / 3).clamp(2, 8) as usize; + self.against(room, Bed, n, n); + self.crowd(room, Role::Patient, 1..=3); + self.against(room, Chest, 0, 1); + } + Stacks => { + self.rows(room, Shelf, 1, 2, true); + self.crowd(room, Role::Reader, 0..=2); + } + Assay => { + self.out_in(room, Workbench, 1, 2); + self.against(room, Crate, 2, 4); + self.crowd(room, Role::Worker, 0..=1); + } + Smithy => { + self.against(room, Forge, 1, 1); + self.out_in(room, Anvil, 1, 2); + self.against(room, Rack, 1, 2); + self.out_in(room, Workbench, 0, 1); + self.crowd(room, Role::Worker, 0..=1); + } + Yard => { + self.out_in(room, Engine, 2, 4); + self.against(room, Crate, 2, 5); + self.out_in(room, Anvil, 0, 1); + self.crowd(room, Role::Worker, 1..=3); + } + Hall => { + self.runner(room); + self.against(room, Statue, 2, 4); + self.against(room, Plant, 1, 3); + self.against(room, Lamp, 1, 2); + } + Dining => { + let seats = self.long_table(room); + self.against(room, Shelf, 0, 1); + if self.rng.random_bool(0.4) { + self.seat(&seats, Role::Resident, 1..=1); + } + self.crowd(room, Role::Servant, 0..=1); + } + Library => { + self.against(room, Shelf, 4, 12); + let seats = self.table(room, 2); + self.seat(&seats, Role::Reader, 0..=1); + } + Bedchamber => { + self.against(room, Bed, 1, 1); + self.against(room, Chest, 1, 1); + self.against(room, Lamp, 0, 1); + self.out_in(room, Plant, 0, 1); + self.rug(room, 0.7); + } + Gallery => { + self.against(room, Statue, 3, 6); + self.out_in(room, Plant, 1, 2); + } + Purpose::Throne => { + self.runner(room); + let f = self.frame(room); + let mid = f.len_u() / 2; + // The throne at the end of the runner, lamps beside it. + let throne = f.at(mid, 0); + if self.tile(throne) == Tile::Rug { + self.site.set(throne, Tile::Floor); + } + if self.put(throne, Thing::Throne) { + self.add(throne, Role::Ruler, true); + } + for u in [mid - 2, mid + 2] { + self.put(f.at(u, 0), Lamp); + } + // Pillars down both sides, guards along the runner. + let mut v = 2; + while v < f.len_v() - 2 { + for u in [mid - 4, mid + 4] { + if u > 0 && u < f.len_u() - 1 { + self.put(f.at(u, v), Pillar); + } + } + v += 3; + } + let mut guards = 0; + for v in (2..f.len_v() - 1).step_by(4) { + for u in [mid - 2, mid + 2] { + let p = f.at(u, v); + if guards < 6 && self.tile(p).walkable() && self.site.person_at(p).is_none() + { + self.add(p, Role::Guard, true); + guards += 1; + } + } + } + self.crowd(room, Role::Courtier, 2..=4); + } + Council => { + let seats = self.long_table(room); + self.seat(&seats, Role::Courtier, 1..=4); + self.against(room, Shelf, 0, 2); + } + Treasury => { + self.against(room, Chest, 4, 9); + self.crowd(room, Role::Guard, 1..=1); + } + Nave => { + let altar = self.at_back(room, Altar); + let f = self.frame(room); + let mid = f.len_u() / 2; + for u in [mid - 2, mid + 2] { + self.put(f.at(u, 0), Lamp); + } + if let Some(at) = altar { + self.add(at, Role::Priest, true); + } + let pews = self.rows(room, Pew, 3, 2, true); + let n = self.rng.random_range(2..=(pews.len() / 3).max(3)); + self.seat(&pews, Role::Worshipper, n..=n); + self.crowd(room, Role::Pilgrim, 0..=2); + } + Vestry => { + self.against(room, Shelf, 1, 2); + self.against(room, Chest, 1, 1); + self.table(room, 1); + } + Cell => { + self.against(room, Bed, 1, 1); + self.against(room, Chest, 0, 1); + if self.rng.random_bool(0.5) { + self.crowd(room, Role::Priest, 1..=1); + } + } + Shrine => { + if let Some(at) = self.at_back(room, Altar) { + self.add(at, Role::Priest, true); + } + let f = self.frame(room); + let mid = f.len_u() / 2; + for u in [mid - 2, mid + 2] { + self.put(f.at(u, 0), Lamp); + } + let benches = self.rows(room, Bench, 3, 3, true); + self.seat(&benches, Role::Worshipper, 0..=2); + self.crowd(room, Role::Pilgrim, 0..=2); + } + Workroom => { + match culture { + Culture::Cant => { + self.against(room, Forge, 1, 1); + self.out_in(room, Anvil, 1, 2); + self.against(room, Rack, 0, 1); + } + Culture::Qaari => { + self.out_in(room, Loom, 1, 2); + self.against(room, Barrel, 1, 2); + } + Culture::Houses => { + self.out_in(room, Workbench, 1, 3); + self.against(room, Shelf, 1, 2); + } + Culture::Drift => { + self.out_in(room, Engine, 1, 1); + self.out_in(room, Workbench, 1, 2); + } + } + self.against(room, Workbench, 1, 2); + self.against(room, Crate, 0, 2); + self.crowd(room, Role::Worker, 1..=3); + } + Storehouse => { + let goods = if self.rng.random_bool(0.6) { Crate } else { Barrel }; + self.rows(room, goods, 1, 2, true); + self.crowd(room, Role::Watchman, 1..=1); + } + Office => { + let seats = self.table(room, 1); + self.against(room, Shelf, 1, 1); + self.seat(&seats, Role::Worker, 0..=1); + } + Changing => { + self.against(room, Bench, 2, 5); + self.against(room, Chest, 1, 2); + self.crowd(room, Role::Worker, 1..=1); + } + Baths => { + let r = self.rooms[room].rect; + let pool = Rect::new(r.left + 3, r.top + 2, r.right - 3, r.bottom - 2); + if pool.width() >= 2 && pool.height() >= 1 { + for p in pool.cells() { + self.put(p, Bath); + } + } + self.crowd(room, Role::Bather, 2..=5); + } + Steam => { + self.against(room, Stove, 1, 1); + let cells = self.by_wall(room); + let seats: Vec<Pos> = + cells.into_iter().filter(|&p| self.put(p, Bench)).take(5).collect(); + self.seat(&seats, Role::Bather, 1..=3); + } + Guardroom => { + let seats = self.table(room, 4); + self.seat(&seats, Role::Guard, 1..=3); + self.against(room, Rack, 1, 3); + self.crowd(room, Role::Guard, 0..=2); + } + Bunks => self.against(room, Bed, 3, 8), + } + } + + /// The people who live in a house or a great house. + fn residents(&mut self) { + let (role, n, servants) = match self.kind { + Kind::House => (Role::Resident, self.rng.random_range(1..=4), 0), + Kind::Manor => { + (Role::Resident, self.rng.random_range(1..=2), self.rng.random_range(1..=3)) + } + Kind::Palace => (Role::Servant, self.rng.random_range(1..=3), 0), + Kind::Workshop => (Role::Worker, self.rng.random_range(0..=1), 0), + _ => return, + }; + let rooms = self.rooms.len(); + for (role, n) in [(role, n), (Role::Servant, servants)] { + for _ in 0..n { + let room = self.rng.random_range(0..rooms); + self.crowd(room, role, 1..=1); + } + } + } +} |