1use std::{mem::swap, ops::RangeInclusive};
2
3use common::{
4 comp::Content,
5 lottery::Lottery,
6 store::{Id, Store},
7 terrain::{BlockKind, SpriteCfg, SpriteKind},
8};
9use enum_map::EnumMap;
10use enumset::EnumSet;
11use hashbrown::HashSet;
12use rand::{
13 Rng,
14 seq::{IteratorRandom, SliceRandom},
15};
16use strum::{EnumIter, IntoEnumIterator};
17use vek::*;
18
19use crate::{
20 IndexRef, Land,
21 site::{Dir, Fill, Site, Structure, gen::PrimitiveTransform, namegen, util::Dir3},
22 util::RandomField,
23};
24
25type Neighbor = Option<Id<Room>>;
26
27pub struct Wall {
28 start: Vec2<i32>,
29 end: Vec2<i32>,
30 base_alt: i32,
31 top_alt: i32,
32 from: Neighbor,
33 to: Neighbor,
34 to_dir: Dir,
35 door: Option<(i32, i32)>,
36}
37
38impl Wall {
39 pub fn door_pos(&self) -> Option<Vec3<f32>> {
40 let wall_dir = Dir::from_vec2(self.end - self.start);
41
42 self.door.map(|(door_min, door_max)| {
43 (self.start.as_() + wall_dir.to_vec2().as_() * (door_min + door_max) as f32 / 2.0 + 0.5)
44 .with_z(self.base_alt as f32)
45 })
46 }
47
48 pub fn door_bounds(&self) -> Option<Aabr<i32>> {
49 let wall_dir = Dir::from_vec2(self.end - self.start);
50
51 self.door.map(|(door_min, door_max)| {
52 Aabr {
53 min: self.start + wall_dir.to_vec2() * door_min,
54 max: self.start + wall_dir.to_vec2() * door_max,
55 }
56 .made_valid()
57 })
58 }
59}
60
61#[derive(Copy, Clone)]
62enum RoofStyle {
63 Flat,
64 FlatBars { dir: Dir },
65 LeanTo { dir: Dir, max_z: i32 },
66 Gable { dir: Dir, max_z: i32 },
67 Hip { max_z: i32 },
68 Floor,
69}
70
71struct Roof {
72 bounds: Aabr<i32>,
73 min_z: i32,
74 style: RoofStyle,
75 stairs: Option<(Aabb<i32>, Dir)>,
76}
77
78#[derive(Clone, Copy, EnumIter, enum_map::Enum)]
79enum RoomKind {
80 Garden,
81 Stage,
82 Bar,
83 Seating,
84 Entrance,
85 Cellar,
86}
87
88impl RoomKind {
89 fn size_range(&self) -> (RangeInclusive<i32>, RangeInclusive<i32>) {
91 match self {
92 RoomKind::Garden => (5..=20, 35..=250),
93 RoomKind::Seating => (4..=20, 35..=250),
94 RoomKind::Cellar => (6..=12, 35..=110),
95 RoomKind::Stage => (11..=22, 150..=400),
96 RoomKind::Bar => (9..=16, 80..=196),
97 RoomKind::Entrance => (3..=7, 12..=40),
98 }
99 }
100
101 fn chance(&self, room_counts: &EnumMap<RoomKind, u32>) -> f32 {
102 match self {
103 RoomKind::Garden => 0.05 / (1.0 + room_counts[RoomKind::Garden] as f32).powi(2),
104 RoomKind::Seating => 0.4 / (1.0 + room_counts[RoomKind::Seating] as f32),
105 RoomKind::Stage => match room_counts[RoomKind::Stage] {
106 0 => 1.0,
107 _ => 0.0,
108 },
109 RoomKind::Bar => match room_counts[RoomKind::Bar] {
110 0 => 1.0,
111 1 => 0.01,
112 _ => 0.0,
113 },
114 RoomKind::Entrance => 0.0,
115 RoomKind::Cellar => 1.0,
116 }
117 }
118
119 fn fits(&self, max_bounds: Aabr<i32>) -> bool {
120 let max_min_size = max_bounds.size().reduce_min();
122 let max_area = max_bounds.size().product();
124
125 let (size_range, area_range) = self.size_range();
126 *size_range.start() <= max_min_size && *area_range.start() <= max_area
127 }
128
129 fn entrance_room_lottery(temperature: f32) -> Lottery<RoomKind> {
130 let rooms = [
131 (0.5 * temperature, RoomKind::Garden),
132 (2.0, RoomKind::Entrance),
133 ]
134 .into_iter()
135 .filter(|(c, _)| *c > 0.0)
136 .collect::<Vec<_>>();
137
138 Lottery::from(rooms)
139 }
140
141 fn side_room_lottery(
142 &self,
143 max_bounds: Aabr<i32>,
144 room_counts: &EnumMap<RoomKind, u32>,
145 temperature: f32,
146 ) -> Option<Lottery<RoomKind>> {
147 let rooms: &[RoomKind] = match self {
148 RoomKind::Cellar => &[RoomKind::Cellar],
149 _ => &[
150 RoomKind::Stage,
151 RoomKind::Garden,
152 RoomKind::Bar,
153 RoomKind::Seating,
154 ],
155 };
156 let lottery = rooms.iter()
157 .filter(|kind| kind.fits(max_bounds))
159 .map(|room_kind| {
161 let temp_scale = match room_kind {
162 RoomKind::Garden => temperature,
163 _ => 1.0,
164 };
165
166 (
167 room_kind.chance(room_counts) * temp_scale,
168 *room_kind,
169 )
170 })
171 .filter(|(c, _)| *c > 0.0)
172 .collect::<Vec<_>>();
173
174 if lottery.is_empty() {
175 return None;
176 }
177
178 Some(Lottery::from(lottery))
179 }
180
181 fn basement_rooms(&self) -> &'static [RoomKind] {
182 match self {
183 RoomKind::Bar => &[RoomKind::Cellar],
184 _ => &[],
185 }
186 }
187
188 fn basement_lottery(
189 &self,
190 max_bounds: Aabr<i32>,
191 room_counts: &EnumMap<RoomKind, u32>,
192 ) -> Option<Lottery<RoomKind>> {
193 let lottery = self.basement_rooms().iter()
194 .filter(|kind| kind.fits(max_bounds))
196 .map(|room_kind| {
198 (
199 room_kind.chance(room_counts),
200 *room_kind,
201 )
202 })
203 .collect::<Vec<_>>();
204
205 if lottery.is_empty() {
206 return None;
207 }
208
209 Some(Lottery::from(lottery))
210 }
211}
212
213#[derive(Clone, Copy)]
214pub enum Detail {
215 Bar {
216 aabr: Aabr<i32>,
217 },
218 Table {
219 pos: Vec2<i32>,
220 chairs: EnumSet<Dir>,
221 },
222 Stage {
223 aabr: Aabr<i32>,
224 },
225}
226
227pub struct Room {
228 pub bounds: Aabb<i32>,
230 kind: RoomKind,
231 walls: EnumMap<Dir, Vec<Id<Wall>>>,
233 floors: Vec<Id<Roof>>,
234 roofs: Vec<Id<Roof>>,
235 detail_areas: Vec<Aabr<i32>>,
236 pub details: Vec<Detail>,
237}
238
239impl Room {
240 fn new(bounds: Aabb<i32>, kind: RoomKind) -> Self {
241 Self {
242 bounds,
243 kind,
244 floors: Default::default(),
245 roofs: Default::default(),
246 walls: Default::default(),
247 detail_areas: Default::default(),
248 details: Default::default(),
249 }
250 }
251
252 fn is_covered_by_roof(&self, roofs: &Store<Roof>) -> bool {
254 let aabr = Aabr {
255 min: self.bounds.min.xy(),
256 max: self.bounds.max.xy(),
257 };
258 for roof in self.roofs.iter() {
259 if roofs[*roof].bounds.contains_aabr(aabr) {
260 return true;
261 }
262 }
263 false
264 }
265}
266
267pub struct Tavern {
268 pub name: String,
269 pub rooms: Store<Room>,
270 walls: Store<Wall>,
271 roofs: Store<Roof>,
272 pub door_tile: Vec2<i32>,
274 pub door_wpos: Vec3<i32>,
275 pub bounds: Aabr<i32>,
277}
278
279impl Tavern {
280 pub fn generate(
281 land: &Land,
282 _index: IndexRef,
283 rng: &mut impl Rng,
284 site: &Site,
285 door_tile: Vec2<i32>,
286 door_dir: Dir,
287 tile_aabr: Aabr<i32>,
288 alt: Option<i32>,
289 ) -> Self {
290 let name = namegen::NameGen::location(rng).generate_tavern();
291
292 let mut rooms = Store::default();
293 let mut walls = Store::default();
294 let mut roofs = Store::default();
295 let mut room_counts = EnumMap::<RoomKind, u32>::default();
296
297 let bounds = Aabr {
298 min: site.tile_wpos(tile_aabr.min),
299 max: site.tile_wpos(tile_aabr.max),
300 };
301
302 let ibounds = Aabr {
303 min: bounds.min + 1,
304 max: bounds.max - 2,
305 };
306
307 let door_tile_center = site.tile_center_wpos(door_tile);
308 let door_wpos = door_dir.select_aabr_with(ibounds, door_tile_center);
309 let temperature = land.get_interpolated(door_wpos, |c| c.temp);
310
311 let door_alt = alt.unwrap_or_else(|| land.get_alt_approx(door_wpos).ceil() as i32);
312 let door_wpos = door_wpos.with_z(door_alt);
313
314 fn gen_range_snap(rng: &mut impl Rng, range: RangeInclusive<i32>, snap_max: i32) -> i32 {
315 let res = rng.gen_range(range.clone());
316 if snap_max <= *range.end() && snap_max - res <= 2 {
317 snap_max
318 } else {
319 res
320 }
321 }
322
323 fn place_side_room(
325 room: RoomKind,
326 max_bounds: Aabr<i32>,
327 in_dir: Dir,
328 in_pos: Vec2<i32>,
329 rng: &mut impl Rng,
330 ) -> Option<Aabr<i32>> {
331 let (size_range, area_range) = room.size_range();
332 let min = *size_range.start();
333 let snap_max = in_dir.select(max_bounds.size());
334 let max = snap_max.min(*size_range.end());
335 if max < min {
336 return None;
337 }
338 let size_x = gen_range_snap(rng, min..=max, snap_max);
339
340 let min = ((*area_range.start() + size_x - 1) / size_x).max(*size_range.start());
341 let snap_max = in_dir.orthogonal().select(max_bounds.size());
342 let max = snap_max
343 .min(*size_range.end())
344 .min(*area_range.end() / size_x);
345
346 if max < min {
347 return None;
348 }
349 let size_y = gen_range_snap(rng, min..=max, snap_max);
350
351 let half_size_y = size_y / 2 + (size_y % 2) * rng.gen_range(0..=1);
353 let min = in_pos + in_dir.to_vec2() + in_dir.rotated_cw().to_vec2() * half_size_y;
354 let min = max_bounds.projected_point(min);
355 let max = min + in_dir.to_vec2() * size_x + in_dir.rotated_ccw().to_vec2() * size_y;
356 let max = max_bounds.projected_point(max);
357 let min = max - in_dir.to_vec2() * size_x + in_dir.rotated_cw().to_vec2() * size_y;
358
359 let bounds = Aabr { min, max }.made_valid();
360 Some(bounds)
361 }
362
363 fn place_down_room(
364 room: RoomKind,
365 max_bounds: Aabr<i32>,
366 from_bounds: Aabr<i32>,
367 rng: &mut impl Rng,
368 ) -> Option<Aabr<i32>> {
369 let (size_range, area_range) = room.size_range();
370 let min = Vec2::broadcast(*size_range.start());
371 let max = Vec2::from(max_bounds.size()).map(|e: i32| e.min(*size_range.end()));
372
373 let size_x = gen_range_snap(rng, min.x..=max.x, max_bounds.size().w);
374 let size_y = gen_range_snap(
375 rng,
376 min.y.max(area_range.start() / size_x)..=max.y.min(area_range.end() / size_x),
377 max_bounds.size().h,
378 );
379 let target_size = Vec2::new(size_x, size_y);
380 let dir = Dir::choose(rng);
381 let orth = *[dir.orthogonal(), dir.orthogonal().opposite()]
382 .choose(rng)
383 .unwrap();
384
385 let plane = dir.to_vec2() + orth.to_vec2();
386 let corner = dir.select_aabr_with(from_bounds, orth.select_aabr(from_bounds));
387 let aabr = Aabr {
388 min: corner,
389 max: corner - plane * target_size,
390 }
391 .made_valid();
392
393 let inside = aabr.intersection(max_bounds);
394 let mv = target_size - inside.size();
395 let aabr = Aabr {
396 min: aabr.min + mv * plane,
397 max: aabr.max + mv * plane,
398 };
399
400 let aabr = aabr.intersection(max_bounds);
401
402 let area = aabr.size().product();
403 if aabr.is_valid()
404 && area_range.contains(&area)
405 && size_range.contains(&aabr.size().reduce_min())
406 {
407 Some(aabr)
408 } else {
409 None
410 }
411 }
412
413 struct RoomMeta {
414 id: Id<Room>,
415 free_walls: EnumSet<Dir>,
416 can_add_basement: bool,
417 }
418
419 let mut room_metas = Vec::new();
420
421 {
422 let entrance_rooms = RoomKind::entrance_room_lottery(temperature);
423
424 let entrance_room = *entrance_rooms.choose_seeded(rng.gen());
425 let entrance_room_hgt = rng.gen_range(3..=4);
426 let entrance_room_aabr =
427 place_side_room(entrance_room, ibounds, -door_dir, door_wpos.xy(), rng)
428 .expect("Not enough room in plot for a tavern");
429 let entrance_room_aabb = Aabb {
430 min: entrance_room_aabr.min.with_z(door_wpos.z),
431 max: entrance_room_aabr
432 .max
433 .with_z(door_wpos.z + entrance_room_hgt),
434 }
435 .made_valid();
436
437 let entrance_id = rooms.insert(Room::new(entrance_room_aabb, entrance_room));
438
439 let start = door_dir.select_aabr_with(
440 entrance_room_aabr,
441 Vec2::broadcast(door_dir.rotated_cw().select_aabr(entrance_room_aabr)),
442 ) + door_dir.rotated_cw().to_vec2()
443 + door_dir.to_vec2();
444 let door_center = door_dir.rotated_cw().select(door_wpos.xy() - start).abs();
445 let wall_id = walls.insert(Wall {
446 start,
447 end: door_dir.select_aabr_with(
448 entrance_room_aabr,
449 Vec2::broadcast(door_dir.rotated_ccw().select_aabr(entrance_room_aabr)),
450 ) + door_dir.rotated_ccw().to_vec2()
451 + door_dir.to_vec2(),
452 base_alt: entrance_room_aabb.min.z,
453 top_alt: entrance_room_aabb.max.z,
454 from: None,
455 to: Some(entrance_id),
456 to_dir: -door_dir,
457 door: Some((door_center - 1, door_center + 1)),
458 });
459 rooms[entrance_id].walls[door_dir].push(wall_id);
460
461 room_metas.push(RoomMeta {
462 id: entrance_id,
463 free_walls: Dir::iter().filter(|d| *d != door_dir).collect(),
464 can_add_basement: false,
465 });
466
467 room_counts[entrance_room] += 1;
468 }
469
470 fn to_aabr(aabb: Aabb<i32>) -> Aabr<i32> {
471 Aabr {
472 min: aabb.min.xy(),
473 max: aabb.max.xy(),
474 }
475 }
476
477 fn extend_aabr(aabr: Aabr<i32>, amount: i32) -> Aabr<i32> {
478 Aabr {
479 min: aabr.min - amount,
480 max: aabr.max + amount,
481 }
482 }
483 while !room_metas.is_empty() {
484 let mut room_meta = room_metas.swap_remove(rng.gen_range(0..room_metas.len()));
486 let from_id = room_meta.id;
487 let from_room = &rooms[from_id];
488
489 let from_bounds = to_aabr(from_room.bounds);
490
491 fn fit_room<'r>(
492 rooms: impl Iterator<Item = &'r Room>,
493 min_z: i32,
494 max_z: i32,
495 mut max_bounds: Aabr<i32>,
496 mut max_shrink_dir: impl FnMut(Dir) -> Option<i32>,
497 ) -> Option<Aabr<i32>> {
498 for room in rooms
502 .filter(|room| room.bounds.min.z - 1 <= max_z && room.bounds.max.z + 1 >= min_z)
503 {
504 let test_bounds = to_aabr(room.bounds);
505 let bounds = extend_aabr(test_bounds, 2);
506 let intersection = bounds.intersection(max_bounds);
507 if intersection.is_valid() {
508 let bounds = Dir::iter()
510 .filter(|dir| {
511 dir.select_aabr(intersection) * dir.signum()
512 < dir.select_aabr(max_bounds) * dir.signum()
513 })
514 .map(|min_dir| {
515 let max_shrink = max_shrink_dir(min_dir);
516 let limit = min_dir.vec2_abs(max_shrink, None);
517 Aabr {
518 min: min_dir.select_aabr_with(
519 max_bounds,
520 Vec2::broadcast(
521 min_dir.rotated_ccw().select_aabr(max_bounds),
522 ),
523 ),
524 max: min_dir
525 .select_aabr_with(
526 intersection,
527 Vec2::broadcast(
528 min_dir.rotated_cw().select_aabr(max_bounds),
529 ),
530 )
531 .map2(limit, |a, b| {
532 b.map_or(a, |b| {
533 (a * min_dir.signum()).min(b * min_dir.signum())
534 * min_dir.signum()
535 })
536 }),
537 }
538 .made_valid()
539 })
540 .filter(|bounds| !bounds.intersection(test_bounds).is_valid())
541 .max_by_key(|bounds| bounds.size().product())?;
542
543 max_bounds = bounds;
544 }
545 }
546 Some(max_bounds)
547 }
548 'room_gen: {
549 if let Some(in_dir) = room_meta.free_walls.into_iter().choose(rng) {
550 room_meta.free_walls.remove(in_dir);
551 let right = in_dir.orthogonal();
552 let left = -right;
553
554 let max_bounds = Aabr {
556 min: in_dir.select_aabr_with(from_bounds, ibounds.min)
557 + in_dir.to_vec2() * 2,
558 max: in_dir.select_aabr_with(ibounds, ibounds.max),
559 }
560 .made_valid();
561 let room_hgt = rng.gen_range(3..=5);
563
564 let wanted_alt = land.get_alt_approx(max_bounds.center()) as i32 + 1;
565 let max_stair_length =
566 (in_dir.select(if wanted_alt < from_room.bounds.min.z {
567 from_bounds.size()
568 } else {
569 max_bounds.size()
570 }) / 2)
571 .min(5);
572 let alt = wanted_alt.clamp(
573 from_room.bounds.min.z - max_stair_length,
574 from_room.bounds.min.z + max_stair_length,
575 );
576 let min_z = from_room.bounds.min.z.min(alt);
577 let max_z = from_room.bounds.max.z.max(alt + room_hgt);
578
579 let Some(max_bounds) = fit_room(
580 rooms
581 .iter()
582 .filter(|(id, _)| *id != from_id)
583 .map(|(_, r)| r),
584 min_z,
585 max_z,
586 max_bounds,
587 |dir| {
588 if dir == in_dir {
589 Some(dir.opposite().select_aabr(max_bounds))
592 } else if dir == in_dir.opposite() {
593 None
594 } else {
595 Some(dir.select_aabr(extend_aabr(from_bounds, -1)))
598 }
599 },
600 ) else {
601 break 'room_gen;
602 };
603
604 let Some(room_lottery) = rooms[room_meta.id].kind.side_room_lottery(
605 max_bounds,
606 &room_counts,
607 temperature,
608 ) else {
609 break 'room_gen;
611 };
612
613 let room_kind = *room_lottery.choose_seeded(rng.gen());
614
615 let mut min = left
617 .select_aabr(from_bounds)
618 .max(left.select_aabr(max_bounds));
619 let mut max = right
620 .select_aabr(from_bounds)
621 .min(right.select_aabr(max_bounds));
622 if max < min {
623 swap(&mut min, &mut max);
624 }
625 if min + 2 > max {
626 break 'room_gen;
627 }
628 let in_pos = rng.gen_range(min + 1..=max - 1);
629 let in_pos = in_dir.select_aabr_with(from_bounds, Vec2::broadcast(in_pos))
630 + in_dir.to_vec2();
631
632 let Some(bounds) = place_side_room(room_kind, max_bounds, in_dir, in_pos, rng)
634 else {
635 break 'room_gen;
636 };
637
638 let bounds3 = Aabb {
639 min: bounds.min.with_z(min_z),
640 max: bounds.max.with_z(max_z),
641 };
642 let id = rooms.insert(Room::new(bounds3, room_kind));
643
644 let start = in_dir.select_aabr_with(
645 from_bounds,
646 Vec2::broadcast(
647 left.select_aabr(from_bounds).max(left.select_aabr(bounds)),
648 ),
649 ) + in_dir.to_vec2()
650 + left.to_vec2();
651
652 let end = in_dir.select_aabr_with(
653 from_bounds,
654 Vec2::broadcast(
655 right
656 .select_aabr(from_bounds)
657 .min(right.select_aabr(bounds)),
658 ),
659 ) + in_dir.to_vec2()
660 + right.to_vec2();
661
662 let door_center = right.select(in_pos - start);
663 let b = rng.gen_bool(0.5);
664 let door_min = door_center - b as i32;
665 let door_max = door_center - (!b) as i32;
666 let wall_id = walls.insert(Wall {
667 start,
668 end,
669 base_alt: min_z,
670 top_alt: max_z,
671 from: Some(from_id),
672 to: Some(id),
673 to_dir: in_dir,
674 door: Some((door_min, door_max)),
675 });
676
677 rooms[id].walls[-in_dir].push(wall_id);
678 rooms[from_id].walls[in_dir].push(wall_id);
679
680 room_metas.push(RoomMeta {
681 id,
682 free_walls: Dir::iter().filter(|d| *d != -in_dir).collect(),
683 can_add_basement: !room_kind.basement_rooms().is_empty(),
684 });
685 room_counts[room_kind] += 1;
686 } else if room_meta.can_add_basement {
687 room_meta.can_add_basement = false;
688 let max_bounds = ibounds;
689
690 let room_hgt = rng.gen_range(3..=5);
692 let max_z = from_room.bounds.min.z - 2;
693 let min_z = max_z - room_hgt;
694
695 let Some(max_bounds) = fit_room(
696 rooms
697 .iter()
698 .filter(|(id, _)| *id != from_id)
699 .map(|(_, r)| r),
700 min_z,
701 max_z,
702 max_bounds,
703 |dir| Some(dir.opposite().select_aabr(extend_aabr(from_bounds, -2))),
704 ) else {
705 break 'room_gen;
706 };
707
708 let Some(room_lottery) = rooms[room_meta.id]
709 .kind
710 .basement_lottery(max_bounds, &room_counts)
711 else {
712 break 'room_gen;
714 };
715
716 let room_kind = *room_lottery.choose_seeded(rng.gen());
717
718 let Some(bounds) = place_down_room(room_kind, max_bounds, from_bounds, rng)
720 else {
721 break 'room_gen;
722 };
723
724 let bounds3 = Aabb {
725 min: bounds.min.with_z(min_z),
726 max: bounds.max.with_z(max_z),
727 };
728 let id = rooms.insert(Room::new(bounds3, room_kind));
729
730 room_metas.push(RoomMeta {
731 id,
732 free_walls: EnumSet::all(),
733 can_add_basement: !room_kind.basement_rooms().is_empty(),
734 });
735 room_counts[room_kind] += 1;
736 } else {
737 break 'room_gen;
738 };
739 }
740
741 if !room_meta.free_walls.is_empty() || room_meta.can_add_basement {
743 room_metas.push(room_meta);
744 }
745 }
746
747 for from_id in rooms.ids() {
749 let room_bounds = to_aabr(rooms[from_id].bounds);
750 let mut skip = HashSet::new();
751 skip.insert(from_id);
752 let mut wall_ranges = EnumMap::<Dir, Vec<_>>::default();
753 for dir in Dir::iter() {
754 let orth = dir.orthogonal();
755 let range = (orth.select(room_bounds.min), orth.select(room_bounds.max));
756 wall_ranges[dir].push(range);
757 }
758 let mut split_range = |dir: Dir, min: i32, max: i32| {
760 debug_assert!(min <= max);
761 let mut new_ranges = Vec::new();
762 wall_ranges[dir].retain_mut(|(r_min, r_max)| {
763 if *r_min <= max && *r_max >= min {
764 match (*r_min >= min, *r_max <= max) {
765 (true, true) => false,
766 (true, false) => {
767 *r_min = max + 1;
768 true
769 },
770 (false, true) => {
771 *r_max = min - 1;
772 true
773 },
774 (false, false) => {
775 new_ranges.push((max + 1, *r_max));
776 *r_max = min - 1;
777 true
778 },
779 }
780 } else {
781 true
782 }
783 });
784 wall_ranges[dir].extend(new_ranges);
785 };
786 for dir in Dir::iter() {
787 let connected_walls = &mut rooms[from_id].walls[dir];
788 skip.extend(
789 connected_walls
790 .iter()
791 .flat_map(|wall| walls[*wall].from.into_iter().chain(walls[*wall].to)),
792 );
793 let orth = dir.orthogonal();
794 for wall in connected_walls.iter() {
796 let wall = &walls[*wall];
797 let mut min = orth.select(wall.start);
798 let mut max = orth.select(wall.end);
799 if min > max {
800 swap(&mut min, &mut max);
801 }
802 min += 1;
803 max -= 1;
804 split_range(dir, min, max);
805 }
806 }
807
808 for to_id in rooms.ids().filter(|id| !skip.contains(id)) {
810 let a_min_z = rooms[from_id].bounds.min.z;
811 let a_max_z = rooms[from_id].bounds.max.z;
812 let b_min_z = rooms[to_id].bounds.min.z;
813 let b_max_z = rooms[to_id].bounds.max.z;
814 if a_min_z >= b_max_z || a_max_z <= b_min_z {
815 continue;
817 }
818 let min_z = a_min_z.min(b_min_z);
819 let max_z = a_max_z.max(b_max_z);
820 let n_room_bounds = to_aabr(rooms[to_id].bounds);
821
822 let p1 = n_room_bounds.projected_point(room_bounds.center());
823 let p0 = room_bounds.projected_point(p1);
824
825 let to_dir = Dir::from_vec2(p1 - p0);
826
827 let intersection = to_dir
828 .extend_aabr(room_bounds, 1)
829 .intersection(to_dir.opposite().extend_aabr(n_room_bounds, 1));
830
831 if intersection.is_valid() {
832 let start = intersection.min;
833 let end = intersection.max;
834
835 let orth = to_dir.orthogonal();
836
837 let min = orth.select(start);
838 let max = orth.select(end);
839 split_range(to_dir, min, max);
840 let door = if max - min > 2
841 && max_z - min_z > 3
842 && (rooms[from_id].bounds.min.z - rooms[to_id].bounds.min.z).abs() < 4
843 && rng.gen_bool(0.8)
844 {
845 let door_center = rng.gen_range(1..=max - min - 2);
846 Some((door_center, door_center + 1))
847 } else {
848 None
849 };
850
851 let id = walls.insert(Wall {
852 start: start - orth.to_vec2(),
853 end: end + orth.to_vec2(),
854 base_alt: min_z,
855 top_alt: max_z,
856 from: Some(from_id),
857 to: Some(to_id),
858 to_dir,
859 door,
860 });
861
862 rooms[from_id].walls[to_dir].push(id);
863 rooms[to_id].walls[-to_dir].push(id);
864 }
865 }
866 for (dir, ranges) in wall_ranges {
868 for (min, max) in ranges {
869 let start =
870 dir.select_aabr_with(room_bounds, Vec2::broadcast(min - 1)) + dir.to_vec2();
871 let end =
872 dir.select_aabr_with(room_bounds, Vec2::broadcast(max + 1)) + dir.to_vec2();
873
874 let wall_id = walls.insert(Wall {
875 start,
876 end,
877 base_alt: rooms[from_id].bounds.min.z,
878 top_alt: rooms[from_id].bounds.max.z,
879 from: Some(from_id),
880 to: None,
881 to_dir: dir,
882 door: None,
883 });
884
885 rooms[from_id].walls[dir].push(wall_id);
886 }
887 }
888 }
889
890 for room_id in rooms.ids() {
891 let room = &rooms[room_id];
892 if room.is_covered_by_roof(&roofs) {
894 continue;
895 }
896 let roof_min_z = room.bounds.max.z + 1;
897 let mut roof_bounds = to_aabr(room.bounds);
898 roof_bounds.min -= 2;
899 roof_bounds.max += 2;
900 let mut dirs = Vec::from(Dir::ALL);
901
902 let mut over_rooms = vec![room_id];
903 let mut under_rooms = vec![];
904 while !dirs.is_empty() {
906 let dir = dirs.swap_remove(rng.gen_range(0..dirs.len()));
907 let orth = dir.orthogonal();
908 for (room_id, room) in rooms.iter() {
910 let room_aabr = to_aabr(room.bounds);
911 if room.bounds.max.z + 1 == roof_min_z
912 && dir.select_aabr(roof_bounds) + dir.signum()
913 == (-dir).select_aabr(room_aabr)
914 && orth.select_aabr(roof_bounds) <= orth.select_aabr(room_aabr) + 2
915 && (-orth).select_aabr(roof_bounds) >= (-orth).select_aabr(room_aabr) - 2
916 {
917 if room.is_covered_by_roof(&roofs) {
920 break;
921 }
922 roof_bounds = dir.extend_aabr(roof_bounds, dir.select(room_aabr.size()));
923 dirs.push(dir);
924 over_rooms.push(room_id);
925 break;
926 }
927 }
928 }
929 for (room_id, room) in rooms.iter() {
930 let room_aabr = to_aabr(room.bounds);
931 if room.bounds.min.z - 1 == roof_min_z && room_aabr.collides_with_aabr(roof_bounds)
932 {
933 under_rooms.push(room_id);
934 }
935 }
936
937 let valid_styles = if !under_rooms.is_empty() {
938 vec![(1.0, RoofStyle::Floor)]
939 } else {
940 let mut valid_styles = vec![(0.5, RoofStyle::Flat)];
942
943 let gardens = over_rooms
944 .iter()
945 .filter(|id| matches!(rooms[**id].kind, RoomKind::Garden))
946 .count();
947
948 if gardens == over_rooms.len() {
950 let ratio = Dir::X.select(roof_bounds.size()) as f32
951 / Dir::Y.select(roof_bounds.size()) as f32;
952 valid_styles.extend([
953 (5.0 * ratio, RoofStyle::FlatBars { dir: Dir::X }),
954 (5.0 / ratio, RoofStyle::FlatBars { dir: Dir::Y }),
955 ]);
956 }
957
958 let mut dir_zs = EnumMap::default();
960 for dir in Dir::iter() {
961 let orth = dir.orthogonal();
962 for room in rooms.values() {
963 let room_aabr = to_aabr(room.bounds);
964 if room.bounds.max.z > roof_min_z
965 && dir.select_aabr(roof_bounds) == (-dir).select_aabr(room_aabr)
966 && orth.select_aabr(roof_bounds) <= orth.select_aabr(room_aabr) + 2
967 && (-orth).select_aabr(roof_bounds)
968 >= (-orth).select_aabr(room_aabr) - 2
969 {
970 dir_zs[dir] = Some(room.bounds.max.z);
971 break;
972 }
973 }
974 }
975
976 for dir in [Dir::X, Dir::Y] {
977 if dir_zs[dir.orthogonal()].is_none() && dir_zs[-dir.orthogonal()].is_none() {
978 let max_z = roof_min_z
979 + (dir.orthogonal().select(roof_bounds.size()) / 2 - 1).min(7);
980 let max_z = match (dir_zs[dir], dir_zs[-dir]) {
981 (Some(a), Some(b)) => {
982 if a.min(b) >= roof_min_z + 3 {
983 max_z.min(a.min(b))
984 } else {
985 max_z
986 }
987 },
988 (None, None) => max_z,
989 _ => continue,
990 };
991
992 for max_z in roof_min_z + 3..=max_z {
993 valid_styles.push((1.0, RoofStyle::Gable { dir, max_z }))
994 }
995 }
996 }
997
998 for dir in Dir::iter() {
999 if let (Some(h), None) = (dir_zs[dir], dir_zs[-dir]) {
1000 for max_z in roof_min_z + 2..=h {
1001 valid_styles.push((1.0, RoofStyle::LeanTo { dir, max_z }))
1002 }
1003 }
1004 }
1005
1006 if Dir::iter().all(|d| dir_zs[d].is_none()) {
1007 for max_z in roof_min_z + 3..=roof_min_z + 7 {
1008 valid_styles.push((0.8, RoofStyle::Hip { max_z }))
1009 }
1010 }
1011
1012 valid_styles
1013 };
1014
1015 let style_lottery = Lottery::from(valid_styles);
1016
1017 debug_assert!(
1018 roof_bounds.is_valid(),
1019 "Roof bounds aren't valid: {:?}",
1020 roof_bounds
1021 );
1022
1023 let stairs = under_rooms
1024 .iter()
1025 .copied()
1026 .flat_map(|to_room| {
1027 let rooms = &rooms;
1028 let walls = &walls;
1029 over_rooms
1030 .iter()
1031 .copied()
1032 .filter_map(move |in_room| {
1033 let to_room_bounds = rooms[to_room].bounds;
1034 let in_room_bounds = rooms[in_room].bounds;
1035 let max_bounds =
1036 to_aabr(to_room_bounds).intersection(to_aabr(in_room_bounds));
1037 let stair_length = to_room_bounds.min.z - 1 - in_room_bounds.min.z;
1038 if !max_bounds.is_valid()
1039 || max_bounds.size().reduce_min() <= stair_length
1040 {
1041 return None;
1042 }
1043
1044 let in_aabr = to_aabr(in_room_bounds);
1045 let to_aabr = to_aabr(to_room_bounds);
1046
1047 let valid_dirs = Dir::iter().filter(move |dir| {
1048 dir.select_aabr(in_aabr) == dir.select_aabr(max_bounds)
1049 || dir.select_aabr(to_aabr) == dir.select_aabr(max_bounds)
1050 });
1051
1052 Some(valid_dirs.clone().flat_map(move |dir| {
1053 valid_dirs
1054 .clone()
1055 .filter(move |d| d.abs() != dir.abs())
1056 .filter_map(move |orth| {
1057 let stair_width = 2;
1058 let stair_aabr = orth.trim_aabr(
1059 dir.trim_aabr(
1060 max_bounds,
1061 dir.select(max_bounds.size()) - stair_length,
1062 ),
1063 orth.select(max_bounds.size()) - stair_width + 1,
1064 );
1065
1066 let test_aabr = Aabr {
1067 min: stair_aabr.min - 1,
1068 max: stair_aabr.max - 1,
1069 };
1070 if !stair_aabr.is_valid()
1071 || rooms[in_room]
1072 .walls
1073 .values()
1074 .chain(rooms[to_room].walls.values())
1075 .flatten()
1076 .any(|wall| {
1077 walls[*wall].door_bounds().is_some_and(
1078 |door_bounds| {
1079 test_aabr
1080 .collides_with_aabr(door_bounds)
1081 },
1082 )
1083 })
1084 {
1085 return None;
1086 }
1087
1088 Some((
1089 Aabb {
1090 min: stair_aabr.min.with_z(in_room_bounds.min.z),
1091 max: stair_aabr
1092 .max
1093 .with_z(to_room_bounds.min.z - 1),
1094 },
1095 dir,
1096 ))
1097 })
1098 }))
1099 })
1100 .flatten()
1101 })
1102 .choose(rng);
1103
1104 let roof_id = roofs.insert(Roof {
1105 bounds: roof_bounds,
1106 min_z: roof_min_z,
1107 stairs,
1108 style: *style_lottery.choose_seeded(rng.gen()),
1109 });
1110
1111 for room_id in over_rooms {
1112 rooms[room_id].roofs.push(roof_id);
1113 }
1114 for room_id in under_rooms {
1115 rooms[room_id].floors.push(roof_id);
1116 }
1117 }
1118
1119 for room in rooms.values_mut() {
1121 let bounds = to_aabr(room.bounds);
1122 let walls = &walls;
1123 let mut avoid = room
1124 .walls
1125 .iter()
1126 .flat_map(|(dir, dir_walls)| {
1127 dir_walls.iter().filter_map(move |wall_id| {
1128 let wall = &walls[*wall_id];
1129
1130 let door_bounds = wall.door_bounds()?;
1131
1132 Some(
1133 Aabr {
1134 min: dir.select_aabr_with(bounds, door_bounds.min),
1135 max: dir.select_with(bounds.center(), door_bounds.max),
1136 }
1137 .made_valid(),
1138 )
1139 })
1140 })
1141 .chain(
1142 room.floors
1143 .iter()
1144 .chain(room.roofs.iter())
1145 .filter_map(|roof| {
1146 let aabr = to_aabr(roofs[*roof].stairs?.0);
1147 let intersection = aabr.intersection(bounds);
1148 intersection.is_valid().then_some(intersection)
1149 }),
1150 )
1151 .collect::<Vec<_>>();
1152
1153 let mut x = bounds.min.x;
1154 while x <= bounds.max.x {
1156 let mut y = bounds.min.y;
1157 'y_loop: while y <= bounds.max.y {
1158 let min = Vec2::new(x, y);
1159 let mut max_y = bounds.max.y;
1160 for area in avoid.iter() {
1161 let contains_x = area.min.x <= min.x && min.x <= area.max.x;
1162 let contains_y = area.min.y <= min.y && min.y <= area.max.y;
1163 if contains_x && contains_y {
1164 y = area.max.y + 1;
1165 continue 'y_loop;
1166 }
1167
1168 if contains_x && min.y < area.min.y && area.min.y - 1 < max_y {
1169 max_y = area.min.y - 1;
1170 }
1171 }
1172
1173 let max_x = avoid
1174 .iter()
1175 .filter_map(|area| {
1176 if area.min.x > x && area.min.y <= max_y && area.max.y >= min.y {
1177 Some(area.min.x - 1)
1178 } else {
1179 None
1180 }
1181 })
1182 .min()
1183 .unwrap_or(bounds.max.x);
1184
1185 let area = Aabr {
1186 min,
1187 max: Vec2::new(max_x, max_y),
1188 };
1189 avoid.push(area);
1190 room.detail_areas.push(area);
1191 y = max_y + 1;
1192 }
1193 x += 1;
1194 }
1195 }
1196
1197 for room in rooms.values_mut() {
1199 let room_aabr = to_aabr(room.bounds);
1200 let table = |pos: Vec2<i32>, aabr: Aabr<i32>| Detail::Table {
1201 pos,
1202 chairs: Dir::iter()
1203 .filter(|dir| aabr.contains_point(pos + dir.to_vec2()))
1204 .collect(),
1205 };
1206 match room.kind {
1207 RoomKind::Garden | RoomKind::Seating => room.detail_areas.retain(|&aabr| {
1208 if aabr.size().reduce_max() > 1 && rng.gen_bool(0.7) {
1209 room.details.push(table(aabr.center(), aabr));
1210 false
1211 } else {
1212 true
1213 }
1214 }),
1215 RoomKind::Cellar => {},
1216 RoomKind::Stage => {
1217 let mut best = None;
1218 let mut best_score = 0;
1219 for (i, aabr) in room.detail_areas.iter().enumerate() {
1220 let edges = Dir::iter()
1221 .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1222 .count() as i32;
1223 let test_score = edges * aabr.size().product();
1224 if best_score < test_score {
1225 best_score = test_score;
1226 best = Some(i);
1227 }
1228 }
1229 if let Some(aabr) = best.map(|i| room.detail_areas.swap_remove(i)) {
1230 room.details.push(Detail::Stage { aabr })
1231 }
1232 room.detail_areas.retain(|&aabr| {
1233 if aabr.size().reduce_max() > 1 && rng.gen_bool(0.8) {
1234 room.details.push(table(aabr.center(), aabr));
1235 false
1236 } else {
1237 true
1238 }
1239 });
1240 },
1241 RoomKind::Bar => {
1242 let mut best = None;
1243 let mut best_score = 0;
1244 for (i, aabr) in room.detail_areas.iter().enumerate() {
1245 let test_score = Dir::iter()
1246 .any(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1247 as i32
1248 * aabr.size().product();
1249 if best_score < test_score {
1250 best_score = test_score;
1251 best = Some(i);
1252 }
1253 }
1254 if let Some(aabr) = best.map(|i| room.detail_areas.swap_remove(i)) {
1255 room.details.push(Detail::Bar { aabr })
1256 }
1257 room.detail_areas.retain(|&aabr| {
1258 if aabr.size().reduce_max() > 1 && rng.gen_bool(0.1) {
1259 room.details.push(table(aabr.center(), aabr));
1260 false
1261 } else {
1262 true
1263 }
1264 });
1265 },
1266 RoomKind::Entrance => {},
1267 }
1268 }
1269
1270 Self {
1271 name,
1272 rooms,
1273 walls,
1274 roofs,
1275 door_tile,
1276 door_wpos,
1277 bounds,
1278 }
1279 }
1280}
1281
1282fn aabb(mut aabb: Aabb<i32>) -> Aabb<i32> {
1283 aabb.make_valid();
1284 aabb.max += 1;
1285 aabb
1286}
1287
1288impl Structure for Tavern {
1289 #[cfg(feature = "use-dyn-lib")]
1290 const UPDATE_FN: &'static [u8] = b"render_tavern\0";
1291
1292 #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_tavern"))]
1293 fn render_inner(&self, _site: &Site, land: &Land, painter: &crate::site::Painter) {
1294 let field = RandomField::new(740384);
1295 let field_choose = RandomField::new(134598);
1296
1297 const DOWN: i32 = 6;
1298
1299 let mut offset = 0;
1300 let mut choose = |slice: &[Rgb<u8>]| -> Rgb<u8> {
1301 offset += 1;
1302 *field
1303 .choose(self.door_wpos + offset, slice)
1304 .expect("Color slice should not be empty.")
1305 };
1306
1307 let detail_fill = Fill::Brick(
1308 BlockKind::Rock,
1309 choose(&[
1310 Rgb::new(55, 65, 64),
1311 Rgb::new(46, 62, 100),
1312 Rgb::new(46, 100, 62),
1313 Rgb::new(100, 100, 105),
1314 ]),
1315 15,
1316 );
1317 let wall_fill = Fill::Brick(
1318 BlockKind::Wood,
1319 choose(&[
1320 Rgb::new(160, 53, 34),
1321 Rgb::new(147, 51, 29),
1322 Rgb::new(147, 101, 69),
1323 Rgb::new(90, 90, 95),
1324 Rgb::new(170, 140, 52),
1325 ]),
1326 20,
1327 );
1328 let wall_detail_fill = Fill::Brick(
1329 BlockKind::Wood,
1330 choose(&[Rgb::new(108, 100, 79), Rgb::new(150, 150, 150)]),
1331 25,
1332 );
1333 let floor_fill = Fill::Brick(
1334 BlockKind::Wood,
1335 choose(&[Rgb::new(42, 44, 43), Rgb::new(56, 18, 10)]),
1336 10,
1337 );
1338 let roof_fill = Fill::Brick(
1339 BlockKind::Wood,
1340 choose(&[
1341 Rgb::new(21, 43, 48),
1342 Rgb::new(11, 23, 38),
1343 Rgb::new(45, 28, 21),
1344 Rgb::new(10, 55, 40),
1345 Rgb::new(5, 35, 15),
1346 Rgb::new(40, 5, 11),
1347 Rgb::new(55, 45, 11),
1348 ]),
1349 20,
1350 );
1351 let simple_roof_fill = Fill::Brick(
1352 BlockKind::Wood,
1353 choose(&[Rgb::new(106, 73, 64), Rgb::new(85, 52, 43)]),
1354 20,
1355 );
1356
1357 let get_kind = |room| self.rooms.get(room).kind;
1358 let get_door_stair = |wall: &Wall, door: Aabr<i32>| {
1359 let filter = |room: &Id<Room>| self.rooms[*room].bounds.min.z > wall.base_alt;
1360 wall.to
1361 .filter(filter)
1362 .zip(Some(wall.to_dir))
1363 .or(wall.from.filter(filter).zip(Some(-wall.to_dir)))
1364 .map(|(room, to_dir)| {
1365 let room = &self.rooms[room];
1366
1367 let max = door.max + to_dir.to_vec2() * (room.bounds.min.z - wall.base_alt + 1);
1368 (door.min, max, room, to_dir)
1369 })
1370 };
1371
1372 for roof in self.roofs.values() {
1374 match roof.style {
1375 RoofStyle::Flat => {
1376 painter
1377 .aabb(aabb(Aabb {
1378 min: roof.bounds.min.with_z(roof.min_z),
1379 max: roof.bounds.max.with_z(roof.min_z),
1380 }))
1381 .fill(roof_fill.clone());
1382 },
1383 RoofStyle::Floor => {
1384 painter
1385 .aabb(aabb(Aabb {
1386 min: roof.bounds.min.with_z(roof.min_z),
1387 max: roof.bounds.max.with_z(roof.min_z),
1388 }))
1389 .fill(floor_fill.clone());
1390 },
1391 RoofStyle::FlatBars { dir } => painter
1392 .aabb(aabb(Aabb {
1393 min: dir
1394 .select_aabr_with(roof.bounds, roof.bounds.min)
1395 .with_z(roof.min_z),
1396 max: dir
1397 .select_aabr_with(roof.bounds, roof.bounds.max)
1398 .with_z(roof.min_z),
1399 }))
1400 .repeat(
1401 -dir.to_vec3() * 2,
1402 (dir.select(roof.bounds.size()) as u32 + 3) / 2,
1403 )
1404 .fill(simple_roof_fill.clone()),
1405 RoofStyle::LeanTo { dir, max_z } => {
1406 painter
1407 .aabb(aabb(Aabb {
1408 min: roof.bounds.min.with_z(roof.min_z),
1409 max: roof.bounds.max.with_z(roof.min_z),
1410 }))
1411 .fill(roof_fill.clone());
1412 painter
1413 .ramp(
1414 aabb(Aabb {
1415 min: roof.bounds.min.with_z(roof.min_z),
1416 max: roof.bounds.max.with_z(max_z),
1417 }),
1418 dir,
1419 )
1420 .fill(roof_fill.clone());
1421 for d in [dir.orthogonal(), -dir.orthogonal()] {
1422 painter
1423 .ramp(
1424 aabb(Aabb {
1425 min: (d.select_aabr_with(roof.bounds, roof.bounds.min)
1426 - d.to_vec2())
1427 .with_z(roof.min_z - 1),
1428 max: (d.select_aabr_with(roof.bounds, roof.bounds.max)
1429 - d.to_vec2())
1430 .with_z(max_z - 1),
1431 }),
1432 dir,
1433 )
1434 .fill(wall_fill.clone());
1435 painter
1436 .ramp(
1437 aabb(Aabb {
1438 min: d
1439 .select_aabr_with(roof.bounds, roof.bounds.min)
1440 .with_z(roof.min_z - 1),
1441 max: d
1442 .select_aabr_with(roof.bounds, roof.bounds.max)
1443 .with_z(max_z - 1),
1444 }),
1445 dir,
1446 )
1447 .clear();
1448 }
1449 },
1450 RoofStyle::Gable { dir, max_z } => {
1451 painter
1452 .gable(
1453 aabb(Aabb {
1454 min: roof.bounds.min.with_z(roof.min_z),
1455 max: roof.bounds.max.with_z(max_z),
1456 }),
1457 max_z - roof.min_z + 1,
1458 dir,
1459 )
1460 .fill(roof_fill.clone());
1461 for dir in [dir, -dir] {
1462 painter
1463 .gable(
1464 aabb(Aabb {
1465 min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1)
1466 - dir.to_vec2())
1467 .with_z(roof.min_z),
1468 max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1)
1469 - dir.to_vec2())
1470 .with_z(max_z - 1),
1471 }),
1472 max_z - roof.min_z,
1473 dir,
1474 )
1475 .fill(wall_fill.clone());
1476 painter
1477 .aabb(aabb(Aabb {
1478 min: (dir.select_aabr_with(roof.bounds, roof.bounds.min + 1)
1479 - dir.to_vec2())
1480 .with_z(roof.min_z),
1481 max: (dir.select_aabr_with(roof.bounds, roof.bounds.max - 1)
1482 - dir.to_vec2())
1483 .with_z(roof.min_z),
1484 }))
1485 .fill(wall_detail_fill.clone());
1486 let center_bounds = Aabr {
1487 min: (dir.select_aabr_with(roof.bounds, roof.bounds.center())
1488 - dir.to_vec2()),
1489 max: (dir.select_aabr_with(
1490 roof.bounds,
1491 (roof.bounds.min + roof.bounds.max + 1) / 2,
1492 ) - dir.to_vec2()),
1493 };
1494 painter
1495 .aabb(aabb(Aabb {
1496 min: center_bounds.min.with_z(roof.min_z),
1497 max: center_bounds.max.with_z(max_z - 1),
1498 }))
1499 .fill(wall_detail_fill.clone());
1500 for d in [dir.orthogonal(), -dir.orthogonal()] {
1501 let hgt = max_z - roof.min_z;
1502 let half_size = d.select(roof.bounds.size() + 1) / 2;
1503 let e = half_size - hgt + 1;
1504 let e = e - e % 2;
1505 let f = half_size - e;
1506 let hgt = (hgt - 1).min(e - f % 2) - (d.signum() - 1) / 2;
1507 let mut aabr = Aabr {
1508 min: d.select_aabr_with(center_bounds, center_bounds.min),
1509 max: d.select_aabr_with(center_bounds, center_bounds.max)
1510 + d.to_vec2() * hgt,
1511 }
1512 .made_valid();
1513 aabr.max += 1;
1514 painter
1515 .plane(
1516 aabr,
1517 aabr.min
1518 .with_z(if d.signum() < 0 {
1519 roof.min_z + hgt
1520 } else {
1521 roof.min_z
1522 })
1523 .as_(),
1524 d.to_vec2().as_(),
1525 )
1526 .fill(wall_detail_fill.clone());
1527 }
1528 painter
1529 .gable(
1530 aabb(Aabb {
1531 min: dir
1532 .select_aabr_with(roof.bounds, roof.bounds.min + 1)
1533 .with_z(roof.min_z),
1534 max: dir
1535 .select_aabr_with(roof.bounds, roof.bounds.max - 1)
1536 .with_z(max_z - 1),
1537 }),
1538 max_z - roof.min_z,
1539 dir,
1540 )
1541 .clear();
1542 }
1543 },
1544 RoofStyle::Hip { max_z } => {
1545 painter
1546 .pyramid(aabb(Aabb {
1547 min: roof.bounds.min.with_z(roof.min_z),
1548 max: roof.bounds.max.with_z(max_z),
1549 }))
1550 .fill(roof_fill.clone());
1551 },
1552 }
1553 }
1554
1555 for room in self.rooms.values() {
1557 painter
1558 .aabb(aabb(Aabb {
1559 min: room.bounds.min.with_z(room.bounds.min.z - DOWN),
1560 max: room.bounds.max.with_z(room.bounds.min.z - 1),
1561 }))
1562 .fill(floor_fill.clone());
1563 }
1564 for wall in self.walls.values() {
1566 let wall_aabb = Aabb {
1567 min: wall.start.with_z(wall.base_alt),
1568 max: wall.end.with_z(wall.top_alt),
1569 };
1570 let wall_dir = Dir::from_vec2(wall.end - wall.start);
1571 match (wall.from.map(get_kind), wall.to.map(get_kind)) {
1572 (Some(RoomKind::Garden), None) | (None, Some(RoomKind::Garden)) => {
1573 let hgt = wall_aabb.min.z..=wall_aabb.max.z;
1574 painter
1575 .column(wall_aabb.min.xy(), hgt.clone())
1576 .fill(wall_detail_fill.clone());
1577 painter
1578 .column(wall_aabb.max.xy(), hgt)
1579 .fill(wall_detail_fill.clone());
1580 let z = (wall.base_alt + wall.top_alt) / 2;
1581
1582 painter
1583 .aabb(aabb(Aabb {
1584 min: (wall_aabb.min + wall_dir.to_vec2()).with_z(wall_aabb.min.z + 1),
1585 max: (wall_aabb.max - wall_dir.to_vec2()).with_z(wall_aabb.max.z - 1),
1586 }))
1587 .clear();
1588
1589 painter.rotated_sprite(
1590 wall_aabb.min.with_z(z) + wall_dir.to_vec2(),
1591 SpriteKind::WallSconce,
1592 wall_dir.sprite_ori_legacy(),
1593 );
1594 painter.rotated_sprite(
1595 wall_aabb.max.with_z(z) - wall_dir.to_vec2(),
1596 SpriteKind::WallSconce,
1597 wall_dir.opposite().sprite_ori_legacy(),
1598 );
1599 painter
1600 .aabb(aabb(Aabb {
1601 min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1602 max: wall_aabb.max.with_z(wall_aabb.min.z),
1603 }))
1604 .fill(wall_detail_fill.clone());
1605 painter
1606 .aabb(aabb(Aabb {
1607 min: wall_aabb.min.with_z(wall_aabb.max.z),
1608 max: wall_aabb.max,
1609 }))
1610 .fill(wall_detail_fill.clone());
1611 },
1612 (Some(RoomKind::Garden), Some(RoomKind::Garden)) => {
1613 painter
1614 .aabb(aabb(Aabb {
1615 min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1616 max: wall_aabb.max.with_z(wall_aabb.min.z - 1),
1617 }))
1618 .fill(floor_fill.clone());
1619 painter.aabb(aabb(wall_aabb)).clear();
1620 },
1621 (None, None) => {},
1622 _ => {
1623 painter
1624 .aabb(aabb(Aabb {
1625 min: wall_aabb.min.with_z(wall_aabb.min.z - DOWN),
1626 max: wall_aabb.max,
1627 }))
1628 .fill(wall_fill.clone());
1629 painter
1630 .column(wall.start, wall.base_alt - DOWN..=wall.top_alt)
1631 .fill(wall_detail_fill.clone());
1632 painter
1633 .column(wall.end, wall.base_alt - DOWN..=wall.top_alt)
1634 .fill(wall_detail_fill.clone());
1635 },
1636 }
1637 if let Some(door) = wall.door_bounds() {
1638 let orth = wall.to_dir.orthogonal();
1639 if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) {
1640 painter
1641 .aabb(aabb(Aabb {
1642 min: (min + to_dir.to_vec2() - orth.to_vec2())
1643 .with_z(wall.base_alt - 1),
1644 max: (max + orth.to_vec2()).with_z(room.bounds.min.z - 1),
1645 }))
1646 .fill(floor_fill.clone());
1647 }
1648 }
1649 }
1650
1651 for room in self.rooms.values() {
1653 painter.aabb(aabb(room.bounds)).clear();
1654
1655 let room_aabr = Aabr {
1656 min: room.bounds.min.xy(),
1657 max: room.bounds.max.xy(),
1658 };
1659 match room.kind {
1660 RoomKind::Garden => {},
1661 RoomKind::Cellar => {
1662 for aabr in room.detail_areas.iter().copied() {
1663 for dir in Dir::iter()
1664 .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr))
1665 {
1666 let pos = dir
1667 .select_aabr_with(aabr, aabr.center())
1668 .with_z(room.bounds.center().z + 1);
1669
1670 painter.rotated_sprite(
1671 pos,
1672 SpriteKind::WallLampSmall,
1673 dir.opposite().sprite_ori_legacy(),
1674 );
1675
1676 for x in dir.orthogonal().select(aabr.min)
1677 ..=dir.orthogonal().select(aabr.max)
1678 {
1679 let pos = dir.select_aabr_with(aabr, x).with_z(room.bounds.min.z);
1680 if field.chance(pos, 0.3) {
1681 let sprite = field_choose
1682 .choose(pos, &[
1683 SpriteKind::Crate,
1684 SpriteKind::Barrel,
1685 SpriteKind::BarrelWoodWater,
1686 ])
1687 .unwrap();
1688 painter.owned_resource_sprite(pos, *sprite, 0);
1689 }
1690 }
1691 }
1692 }
1693 },
1694 RoomKind::Stage => {
1695 for aabr in room.detail_areas.iter().copied() {
1696 for dir in Dir::iter().filter(|dir| {
1697 dir.select_aabr(aabr) == dir.select_aabr(room_aabr)
1698 && dir.rotated_cw().select_aabr(aabr)
1699 == dir.rotated_cw().select_aabr(room_aabr)
1700 }) {
1701 let pos = dir.select_aabr_with(
1702 aabr,
1703 Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)),
1704 );
1705 painter.sprite(pos.with_z(room.bounds.min.z), SpriteKind::StreetLamp);
1706 }
1707 }
1708 },
1709 RoomKind::Bar | RoomKind::Seating => {
1710 for aabr in room.detail_areas.iter().copied() {
1711 for dir in Dir::iter()
1712 .filter(|dir| dir.select_aabr(aabr) == dir.select_aabr(room_aabr))
1713 {
1714 let pos = dir
1715 .select_aabr_with(aabr, aabr.center())
1716 .with_z(room.bounds.center().z);
1717 let orth = dir.orthogonal();
1718 if room.walls[dir].iter().any(|wall| {
1719 let wall = &self.walls[*wall];
1720 (orth.select(wall.start)..=orth.select(wall.end))
1721 .contains(&orth.select(pos))
1722 && (wall.from.is_none() || wall.to.is_none())
1723 }) {
1724 continue;
1725 }
1726
1727 painter.rotated_sprite(
1728 pos,
1729 SpriteKind::WallLampSmall,
1730 dir.opposite().sprite_ori_legacy(),
1731 );
1732 }
1733 }
1734 },
1735 RoomKind::Entrance => {
1736 for aabr in room.detail_areas.iter() {
1737 let edges = Dir::iter()
1738 .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1739 .count();
1740 let hanger_pos = if edges == 2 {
1741 let pos = aabr.center().with_z(room.bounds.min.z);
1742 painter.sprite(pos, SpriteKind::CoatrackMetalWoodland);
1743 Some(pos)
1744 } else {
1745 None
1746 };
1747
1748 for dir in Dir::iter()
1749 .filter(|dir| dir.select_aabr(*aabr) == dir.select_aabr(room_aabr))
1750 {
1751 let pos = dir
1752 .select_aabr_with(*aabr, aabr.center())
1753 .with_z(room.bounds.center().z + 1);
1754 if hanger_pos.is_some_and(|p| p.xy() != pos.xy()) {
1755 painter.rotated_sprite(
1756 pos,
1757 SpriteKind::WallLampSmall,
1758 dir.opposite().sprite_ori_legacy(),
1759 );
1760 }
1761 }
1762 }
1763 },
1764 }
1765 for detail in room.details.iter() {
1766 match *detail {
1767 Detail::Bar { aabr } => {
1768 for dir in Dir::iter() {
1769 let edge = dir.select_aabr(aabr);
1770 let rot_dir = if field.chance(aabr.center().with_z(0), 0.5) {
1771 dir.rotated_cw()
1772 } else {
1773 dir.rotated_ccw()
1774 };
1775 let rot_edge = rot_dir.select_aabr(aabr);
1776 match (
1777 edge == dir.select_aabr(room_aabr),
1778 rot_edge == rot_dir.select_aabr(room_aabr),
1779 ) {
1780 (false, _) => {
1781 let (min, max) = (
1782 dir.select_aabr_with(
1783 aabr,
1784 Vec2::broadcast(rot_dir.select_aabr(aabr)),
1785 ),
1786 dir.select_aabr_with(
1787 aabr,
1788 Vec2::broadcast(rot_dir.opposite().select_aabr(aabr)),
1789 ),
1790 );
1791 painter
1792 .aabb(aabb(Aabb {
1793 min: (min - rot_dir.to_vec2())
1794 .with_z(room.bounds.min.z),
1795 max: max.with_z(room.bounds.min.z),
1796 }))
1797 .fill(wall_detail_fill.clone());
1798 painter
1799 .aabb(aabb(Aabb {
1800 min: min.with_z(room.bounds.min.z + 3),
1801 max: max.with_z(room.bounds.max.z),
1802 }))
1803 .fill(wall_detail_fill.clone());
1804 },
1805 (true, true) => {
1806 painter.sprite(
1807 dir.abs().vec2(edge, rot_edge).with_z(room.bounds.min.z),
1808 SpriteKind::CookingPot,
1809 );
1810 },
1811 (true, false) => {},
1812 }
1813 }
1814 },
1815 Detail::Stage { aabr } => {
1816 painter
1817 .aabb(aabb(Aabb {
1818 min: aabr.min.with_z(room.bounds.min.z),
1819 max: aabr.max.with_z(room.bounds.min.z),
1820 }))
1821 .fill(detail_fill.clone());
1822 painter
1823 .aabb(aabb(Aabb {
1824 min: (aabr.min + 1).with_z(room.bounds.min.z),
1825 max: (aabr.max - 1).with_z(room.bounds.min.z),
1826 }))
1827 .fill(wall_fill.clone());
1828 for dir in Dir::iter().filter(|dir| {
1829 dir.select_aabr(aabr) != dir.select_aabr(room_aabr)
1830 && dir.rotated_cw().select_aabr(aabr)
1831 != dir.rotated_cw().select_aabr(room_aabr)
1832 }) {
1833 let pos = dir.select_aabr_with(
1834 aabr,
1835 Vec2::broadcast(dir.rotated_cw().select_aabr(aabr)),
1836 );
1837 painter
1838 .column(pos, room.bounds.min.z..=room.bounds.max.z)
1839 .fill(wall_detail_fill.clone());
1840
1841 for dir in Dir::iter() {
1842 painter.rotated_sprite(
1843 pos.with_z(room.bounds.center().z + 1) + dir.to_vec2(),
1844 SpriteKind::WallSconce,
1845 dir.sprite_ori_legacy(),
1846 );
1847 }
1848 }
1849 },
1850 Detail::Table { pos, chairs } => {
1851 let pos = pos.with_z(room.bounds.min.z);
1852 painter.sprite(pos, SpriteKind::DiningtableWoodWoodlandSquare);
1853 for dir in chairs.into_iter() {
1854 painter.rotated_sprite(
1855 pos + dir.to_vec2(),
1856 SpriteKind::ChairWoodWoodland2,
1857 dir.opposite().sprite_ori(),
1858 );
1859 }
1860 },
1861 }
1862 }
1863 }
1864
1865 for wall in self.walls.values() {
1867 let kinds = (wall.from.map(get_kind), wall.to.map(get_kind));
1868 let in_dir_room = if let (Some(room), to @ None) | (None, to @ Some(room)) = kinds {
1869 let in_dir = if to.is_none() {
1870 -wall.to_dir
1871 } else {
1872 wall.to_dir
1873 };
1874
1875 Some((in_dir, room))
1876 } else {
1877 None
1878 };
1879 if let Some((in_dir, room)) = in_dir_room {
1880 let width = in_dir.orthogonal().select(wall.end - wall.start).abs();
1881 let wall_center = (wall.start + wall.end) / 2;
1882 let door_dist = wall.door_bounds().map_or(i32::MAX, |door| {
1883 (door.min - wall_center)
1884 .map(|x| x.abs())
1885 .reduce_max()
1886 .max((door.max - wall_center).map(|x| x.abs()).reduce_max())
1887 });
1888 match room {
1889 RoomKind::Garden => {
1890 if door_dist >= 2 {
1891 painter.rotated_sprite(
1892 wall_center.with_z(wall.base_alt + 1),
1893 SpriteKind::Planter,
1894 in_dir.sprite_ori_legacy(),
1895 );
1896 }
1897 },
1898 _ => {
1899 if width >= 5
1900 && door_dist > 3
1901 && wall.base_alt >= land.get_alt_approx(wall_center) as i32
1902 {
1903 painter
1904 .aabb(aabb(Aabb {
1905 min: (wall_center + in_dir.rotated_ccw().to_vec2())
1906 .with_z(wall.base_alt + 1),
1907 max: (wall_center + in_dir.rotated_cw().to_vec2())
1908 .with_z(wall.base_alt + 2),
1909 }))
1910 .fill(Fill::sprite_ori(
1911 SpriteKind::Window1,
1912 in_dir.sprite_ori_legacy(),
1913 ));
1914 }
1915 },
1916 }
1917 }
1918 if let Some(door) = wall.door_bounds()
1919 && !matches!(kinds, (Some(RoomKind::Garden), Some(RoomKind::Garden)))
1920 {
1921 let orth = wall.to_dir.orthogonal();
1922 painter
1923 .aabb(aabb(Aabb {
1924 min: (door.min - orth.to_vec2()).with_z(wall.base_alt),
1925 max: (door.max + orth.to_vec2()).with_z(wall.base_alt + 3),
1926 }))
1927 .fill(detail_fill.clone());
1928 painter
1929 .aabb(aabb(Aabb {
1930 min: (door.min - orth.to_vec2()).with_z(wall.base_alt - 1),
1931 max: (door.max + orth.to_vec2()).with_z(wall.base_alt - 1),
1932 }))
1933 .fill(floor_fill.clone());
1934 painter
1935 .aabb(aabb(Aabb {
1936 min: (door.min + wall.to_dir.to_vec2()).with_z(wall.base_alt),
1937 max: (door.max - wall.to_dir.to_vec2()).with_z(wall.base_alt + 2),
1938 }))
1939 .clear();
1940 if let Some((min, max, room, to_dir)) = get_door_stair(wall, door) {
1941 painter
1943 .ramp(
1944 aabb(Aabb {
1945 min: (min - to_dir.to_vec2() * 3).with_z(wall.base_alt),
1946 max: max.with_z(room.bounds.min.z + 2),
1947 }),
1948 to_dir,
1949 )
1950 .without(
1952 painter
1953 .ramp(
1954 aabb(Aabb {
1955 min: (min + to_dir.to_vec2() * 2).with_z(wall.base_alt),
1956 max: max.with_z(room.bounds.min.z - 1),
1957 }),
1958 to_dir,
1959 )
1960 )
1961 .clear();
1962 }
1963 if let Some((in_dir, _room)) = in_dir_room {
1964 let sprite = match in_dir.rotated_cw().select(door.size()) {
1965 2.. => SpriteKind::DoorWide,
1966 _ => SpriteKind::Door,
1967 };
1968 painter.rotated_sprite(
1969 in_dir
1970 .rotated_cw()
1971 .select_aabr_with(door, door.min)
1972 .with_z(wall.base_alt),
1973 sprite,
1974 in_dir.sprite_ori_legacy(),
1975 );
1976 painter.rotated_sprite(
1977 in_dir
1978 .rotated_ccw()
1979 .select_aabr_with(door, door.min)
1980 .with_z(wall.base_alt),
1981 sprite,
1982 in_dir.opposite().sprite_ori_legacy(),
1983 );
1984
1985 let dir = match field.chance(door.min.with_z(wall.base_alt), 0.5) {
1986 true => in_dir.rotated_cw(),
1987 false => in_dir.rotated_ccw(),
1988 };
1989
1990 let pos =
1991 dir.select_aabr_with(door, door.min) + dir.to_vec2() - in_dir.to_vec2();
1992
1993 painter.rotated_sprite_with_cfg(
1994 pos.with_z(wall.base_alt + 2),
1995 SpriteKind::HangingSign,
1996 in_dir.opposite().sprite_ori_legacy(),
1997 SpriteCfg {
1998 content: Some(Content::Plain(self.name.clone())),
1999 ..Default::default()
2000 },
2001 );
2002 }
2003 }
2004 }
2005
2006 for roof in self.roofs.values() {
2008 if let Some((stairs_aabb, dir)) = roof.stairs {
2009 painter
2010 .aabb(aabb(dir.to_dir3().trim_aabb(
2011 Aabb {
2012 min: stairs_aabb.min.with_z(roof.min_z),
2013 max: stairs_aabb.max.with_z(roof.min_z),
2014 },
2015 (dir.to_dir3().select(stairs_aabb.size()) - 4).max(0),
2016 )))
2017 .clear();
2018 painter
2019 .ramp(aabb(stairs_aabb), dir)
2020 .fill(floor_fill.clone());
2021 painter
2022 .ramp(
2023 aabb(Dir3::NegZ.trim_aabb(dir.to_dir3().trim_aabb(stairs_aabb, 1), 1)),
2024 dir,
2025 )
2026 .clear();
2027 }
2028 }
2029 }
2030}