//! 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, /// Which room each cell is in, `NO_ROOM` for walls and doorways. room_of: Vec, /// 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, } const NO_ROOM: u8 = u8::MAX; impl Interior { pub fn purpose_at(&self, p: Pos) -> Option { 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 { 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, start: Pos, exit: Pos, /// Which side the back wall is on. back: Side, shop: Option, keeper: Option, /// 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, ) -> (Rect, Rect) { let cells: Vec = 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 = 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) { 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 = (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) { 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 = (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 { 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 { let mut cells: Vec = 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 { let mut cells: Vec = 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, 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 { 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 = (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 { 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 { 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 { 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 = (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 { let mut cells: Vec = 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) { 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) { let n = self.rng.random_range(n); let mut seats: Vec = 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 { 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 = 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 = 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); } } } }