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