1use super::*;
2use crate::{
3 ColumnSample, Land,
4 all::ForestKind,
5 site::{
6 generation::PrimitiveTransform,
7 util::sprites::{PainterSpriteExt, single_block},
8 },
9 util::{RandomField, Sampler},
10};
11use common::{
12 assets::AssetHandle,
13 terrain::{Block, BlockKind, StructuresGroup},
14};
15use enum_map::EnumMap;
16use itertools::Itertools;
17use lazy_static::lazy_static;
18use rand::{prelude::*, seq::IndexedRandom};
19use strum::IntoEnumIterator;
20use vek::*;
21
22#[derive(Clone, Copy)]
23enum TownCenterDecoration {
24 Gazebo {
25 roof: GazeboRoofKind,
26 roof_color: Rgb<u8>,
27 },
28 Tree,
29}
30
31#[derive(Clone, Copy)]
32enum GazeboRoofKind {
33 Gable,
34 Pyramid,
35}
36
37#[derive(Clone, Copy)]
38struct MarketStand {
39 aabr: Aabr<i32>,
40 facing: Dir2,
41 alt: i32,
42 roof_color: Rgb<u8>,
43 roof_pattern: Vec2<i32>,
44}
45
46#[derive(Clone)]
47enum PlazaKind {
48 Park {
49 surface_col: Rgb<u8>,
50 center_deco: TownCenterDecoration,
51 },
52 Stage {
53 aabr: Aabr<i32>,
54 facing: Dir2,
55 closed: bool,
56 roof_color: Rgb<u8>,
57 },
58 Market {
59 center: Aabr<i32>,
60 center_deco: Option<TownCenterDecoration>,
61 stands: Vec<MarketStand>,
62 },
63}
64
65#[derive(Default)]
66struct CornerMeta {
67 water_alt: i32,
68 alt: i32,
69}
70
71impl CornerMeta {
72 fn water(&self) -> bool { self.alt < self.water_alt }
73}
74
75pub struct Plaza {
77 pub aabr: Aabr<i32>,
78 pub kind: RoadKind,
79 corner_meta: EnumMap<Dir2, CornerMeta>,
80 pub alt: i32,
81 pub hard_alt: Option<i32>,
82 dir: Dir2,
83 decoration: Option<PlazaKind>,
84 park_surface_col: Rgb<u8>,
85 wood_color: Rgb<u8>,
86}
87
88impl Plaza {
89 pub fn generate(
90 tile_aabr: Aabr<i32>,
91 kind: RoadKind,
92 site: &Site,
93 land: &Land,
94 index: IndexRef,
95 rng: &mut impl Rng,
96 ) -> Self {
97 let aabr = Aabr {
98 min: site.tile_wpos(tile_aabr.min),
99 max: site.tile_wpos(tile_aabr.max),
100 };
101 let mut iaabr = aabr;
102 iaabr.max -= 1;
103
104 let get_corner_meta = |wpos| {
105 land.column_sample(wpos, index)
106 .map(|col| CornerMeta {
107 water_alt: col.water_level as i32,
108 alt: col.alt as i32,
109 })
110 .unwrap_or_default()
111 };
112
113 let center = get_corner_meta(iaabr.center());
114
115 let corner_meta: EnumMap<Dir2, CornerMeta> = Dir2::iter()
116 .map(|d| {
117 let o = d.rotated_cw();
118 let pos = d.select_aabr_with(iaabr, o.select_aabr(iaabr));
119 (d, get_corner_meta(pos))
120 })
121 .collect();
122
123 let any_water = center.water() || corner_meta.values().any(|c| c.water());
124 let alt = land.get_alt_approx(aabr.center()) as i32;
125
126 let hard_alt = if any_water {
127 Some((land.get_alt_approx(aabr.center()) as i32).max(center.water_alt + 1))
128 } else {
129 None
130 };
131
132 let park_surface_col = if let Some(sample) = land.column_sample(aabr.center(), index) {
133 sample.surface_color
134 } else {
135 Rgb::new(0.5, 0.55, 0.0)
136 }
137 .map(|e| (e * 255.0) as u8);
138
139 let min_size = aabr.size().reduce_min();
140 let decoration = if land.get_gradient_approx(aabr.center()) < 1.5
142 && min_size >= TILE_SIZE as i32 * 5
143 && matches!(site.kind, Some(SiteKind::Refactor))
144 {
145 match rng.random_range(0..50) {
146 0..15 => {
147 let stands_aabr = shrink_aabr(aabr, 4);
148 let center_aabr = Aabr {
149 min: stands_aabr.center() - 5,
150 max: stands_aabr.center() + 5,
151 };
152
153 let roof_color = {
154 let colors = [
155 Rgb::new(21, 43, 48),
156 Rgb::new(11, 23, 38),
157 Rgb::new(45, 28, 21),
158 Rgb::new(10, 55, 40),
159 Rgb::new(5, 35, 15),
160 Rgb::new(40, 5, 11),
161 Rgb::new(55, 45, 11),
162 ];
163 *colors.choose(rng).unwrap_or(&Rgb::new(21, 43, 48))
164 };
165
166 let possible_center_deco = if stands_aabr.size().reduce_min() >= 26 {
167 let outcomes = [
168 TownCenterDecoration::Gazebo {
169 roof: GazeboRoofKind::Gable,
170 roof_color,
171 },
172 TownCenterDecoration::Gazebo {
173 roof: GazeboRoofKind::Pyramid,
174 roof_color,
175 },
176 TownCenterDecoration::Tree,
177 ];
178 outcomes.choose(rng).cloned()
179 } else {
180 None
181 };
182
183 let roof_colors = [
184 Rgb::new(0x00, 0x28, 0x68),
185 Rgb::new(0xCE, 0x11, 0x26),
186 Rgb::new(0x68, 0xbf, 0xe5),
187 Rgb::new(0xff, 0xd1, 0x00),
188 Rgb::new(0x00, 0xa6, 0x51),
189 ];
190
191 let mut stand_aabrs = vec![];
192 if possible_center_deco.is_some() {
193 stand_aabrs.push(center_aabr);
194 }
195 let mut stands = Vec::new();
196
197 for _ in 0..24 {
198 if let Some(stand) = attempt(8, || {
199 let offset = Vec2::new(
200 rng.random_range(0..stands_aabr.size().w),
201 rng.random_range(0..stands_aabr.size().h),
202 );
203 let corner = stands_aabr.min + offset;
204 let width = rng.random_range(4..7);
205 let size = 7;
206 let facing_possibilities = Dir2::ALL
207 .into_iter()
208 .filter_map(|d| {
209 let score = if d.is_positive() {
210 d.select(stands_aabr.max - corner)
211 } else {
212 d.select(corner - stands_aabr.min)
213 }
214 .pow(2);
215 stands_aabr
216 .contains_point(corner + d.scale(width + 2))
217 .then_some((d, score))
218 })
219 .collect_vec();
220 let facing = facing_possibilities
221 .choose_weighted(rng, |(_, w)| *w)
222 .map(|(d, _)| *d)
223 .ok()?;
224 let side = facing.choose_orthogonal(rng);
225
226 let stand_aabr = Aabr {
227 min: corner,
228 max: corner + side.opposite().scale(size) + facing.scale(width),
229 }
230 .made_valid();
231
232 let roof_pattern = if rng.random_bool(0.3) {
233 Vec2::one() * rng.random_range(2..4)
234 } else if rng.random_bool(0.5) {
235 facing.scale(width * 10) + side.scale(rng.random_range(1..4))
236 } else {
237 side.scale(size * 10) + facing.scale(rng.random_range(1..4))
238 };
239 let is_even = aabr_corners(stand_aabr)
240 .map(|p| land.get_alt_approx(p) as i32)
241 .iter()
242 .all_equal();
243 (is_even
244 && stands_aabr.contains_aabr(stand_aabr)
245 && stand_aabrs.iter().all(|aabr| {
246 !shrink_aabr(*aabr, -2).intersection(stand_aabr).is_valid()
247 }))
248 .then_some(MarketStand {
249 aabr: stand_aabr,
250 facing,
251 alt: hard_alt.unwrap_or_else(|| {
252 land.get_alt_approx(stand_aabr.center()) as i32
253 }),
254 roof_color: *roof_colors
255 .choose(rng)
256 .unwrap_or(&Rgb::new(0x00, 0x28, 0x68)),
257 roof_pattern,
258 })
259 }) {
260 stand_aabrs.push(stand.aabr);
261 stands.push(stand);
262 }
263 }
264 Some(PlazaKind::Market {
265 center: center_aabr,
266 center_deco: possible_center_deco,
267 stands,
268 })
269 },
270 15..20 => {
271 let center = aabr.center();
272 let aabr = Aabr {
273 min: center - aabr.half_size() / 3,
274 max: center + aabr.half_size() / 3,
275 };
276 let facing = Dir2::choose(rng);
277 let facing_size = facing.select(aabr.size());
278 let aabr = facing.opposite().trim_aabr(
279 aabr,
280 rng.random_range(facing_size / 12..(facing_size / 8) + 1),
281 );
282 let closed = rng.random_bool(0.5);
283 let aabr = if closed {
284 facing.opposite().translate_aabr(aabr, 4)
285 } else {
286 aabr
287 };
288 let roof_color = {
289 let colors = [
290 Rgb::new(21, 43, 48),
291 Rgb::new(11, 23, 38),
292 Rgb::new(45, 28, 21),
293 Rgb::new(10, 55, 40),
294 Rgb::new(5, 35, 15),
295 Rgb::new(40, 5, 11),
296 Rgb::new(55, 45, 11),
297 ];
298 *colors.choose(rng).unwrap_or(&Rgb::new(21, 43, 48))
299 };
300 Some(PlazaKind::Stage {
301 aabr,
302 facing,
303 closed,
304 roof_color,
305 })
306 },
307 20..35 => {
308 let roof_color = {
309 let colors = [
310 Rgb::new(21, 43, 48),
311 Rgb::new(11, 23, 38),
312 Rgb::new(45, 28, 21),
313 Rgb::new(10, 55, 40),
314 Rgb::new(5, 35, 15),
315 Rgb::new(40, 5, 11),
316 Rgb::new(55, 45, 11),
317 ];
318 *colors.choose(rng).unwrap_or(&Rgb::new(21, 43, 48))
319 };
320 let center_decos = [
321 TownCenterDecoration::Tree,
322 TownCenterDecoration::Gazebo {
323 roof: GazeboRoofKind::Gable,
324 roof_color,
325 },
326 TownCenterDecoration::Gazebo {
327 roof: GazeboRoofKind::Pyramid,
328 roof_color,
329 },
330 ];
331 Some(PlazaKind::Park {
332 surface_col: park_surface_col,
333 center_deco: *center_decos.choose(rng).expect("center_decos is not empty"),
334 })
335 },
336 _ => None,
337 }
338 } else {
339 None
340 };
341 let wood_color = match land
342 .make_forest_lottery(aabr.center())
343 .choose_seeded(rng.random())
344 {
345 Some(
346 ForestKind::Cedar
347 | ForestKind::AutumnTree
348 | ForestKind::Frostpine
349 | ForestKind::Mangrove,
350 ) => Rgb::new(63, 28, 12),
351 Some(ForestKind::Oak | ForestKind::Swamp | ForestKind::Baobab) => Rgb::new(102, 87, 63),
352 Some(ForestKind::Acacia | ForestKind::Birch | ForestKind::Palm) => {
353 Rgb::new(130, 104, 102)
354 },
355 Some(
356 ForestKind::Mapletree | ForestKind::Redwood | ForestKind::Pine | ForestKind::Cherry,
357 ) => Rgb::new(117, 95, 46),
358 _ => Rgb::new(63, 28, 12),
359 };
360 Self {
361 aabr,
362 kind,
363 corner_meta,
364 alt,
365 hard_alt,
366 dir: *RandomField::new(51)
367 .choose(aabr.center().with_z(center.alt), &Dir2::ALL)
368 .expect("Dir::ALL has len 4"),
369 decoration,
370 park_surface_col,
371 wood_color,
372 }
373 }
374}
375
376impl Structure for Plaza {
377 #[cfg(feature = "dyn-lib")]
378 #[unsafe(export_name = "as_dyn_structure_plaza")]
379 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
380 Some((Self::as_dyn_impl(self), "as_dyn_structure_plaza"))
381 }
382
383 fn spawn_rules_inner(
384 &self,
385 spawn_rules: &mut SpawnRules,
386 _land: &Land,
387 _wpos: Vec2<i32>,
388 weight: f32,
389 ) {
390 spawn_rules.prefer_alt(self.alt as f32, weight + weight.powi(1) * 45.0);
391 }
392
393 fn render_ordering(&self) -> u32 { 2 }
394
395 fn render_inner(&self, site: &Site, land: &Land, painter: &Painter) {
396 if let Some(alt) = self.hard_alt {
397 let wood_corner = Fill::Brick(BlockKind::Wood, Rgb::new(86, 50, 50), 10);
398 let bounds = Aabb {
399 min: self.aabr.min.with_z(alt),
400 max: self.aabr.max.with_z(alt + 1),
401 };
402 painter.aabb(bounds).fill(wood_corner.clone());
403 let mut iaabr = self.aabr;
404 let hsize = iaabr.half_size();
405 iaabr.min += 1;
406 iaabr.max -= 2;
407 for (d, meta) in self.corner_meta.iter() {
408 let o = d.rotated_cw();
409
410 let corner = d.select_aabr_with(iaabr, o.select_aabr(iaabr));
411
412 painter
413 .column(corner, meta.alt - 5..alt)
414 .fill(wood_corner.clone());
415
416 for (dir, index) in [(d, d.rotated_ccw()), (o, o)] {
417 let dp = -dir;
418 let min_alt = meta.alt.min(self.corner_meta[d.relative_to(index)].alt);
419 for i in (0..dp.select(hsize)).step_by(8) {
420 let p = corner + dp.to_vec2() * i;
421 painter
422 .column(p, min_alt - 5..alt)
423 .fill(wood_corner.clone());
424 }
425 }
426 }
427 }
428
429 let tile_aabr = Aabr {
430 min: site.wpos_tile_pos(self.aabr.min),
431 max: site.wpos_tile_pos(self.aabr.max) - 1,
432 };
433
434 for dir in Dir2::iter() {
435 let orth = dir.orthogonal();
436
437 for i in (orth.select(tile_aabr.min) + 1)..orth.select(tile_aabr.max) {
438 let tpos = dir.select_aabr_with(tile_aabr, i);
439 if (tpos.x + tpos.y) % 3 != 0 {
440 continue;
441 }
442
443 if site.tiles.get(tpos + dir.to_vec2()).is_empty() {
444 let wpos = site.tile_center_wpos(tpos);
445
446 let alt = self.hard_alt.unwrap_or(self.alt) + 1;
448 let wpos = wpos.with_z(alt);
449 self.kind.place_light(wpos, -dir, painter);
450 }
451 }
452 }
453
454 let rng = &mut rand::rng();
455 if rng.random_bool(0.5) {
456 let spec = [
457 "common.entity.wild.peaceful.cat",
458 "common.entity.wild.peaceful.dog",
459 ]
460 .choose(rng)
461 .unwrap();
462 let wpos_2d = Vec2::new(
463 rng.random_range(self.aabr.min.x..self.aabr.max.x),
464 rng.random_range(self.aabr.min.y..self.aabr.max.y),
465 );
466 painter.spawn(
467 EntityInfo::at(wpos_2d.with_z(self.hard_alt.unwrap_or(self.alt)).as_())
468 .with_asset_expect(spec, rng, None)
469 .with_alignment(Alignment::Tame),
470 );
471 }
472
473 if let Some(decoration) = self.decoration.clone() {
474 let wood = Fill::PlankWall(BlockKind::Wood, self.wood_color, 4);
475 let dark_wood = Fill::PlankWall(BlockKind::Wood, self.wood_color / 2, 4);
476 let darker_wood = Fill::PlankWall(BlockKind::Wood, self.wood_color / 4, 4);
477 let stone = Fill::Brick(BlockKind::Rock, Rgb::new(70, 65, 80), 24);
478 match decoration {
479 PlazaKind::Park {
480 surface_col,
481 center_deco,
482 } => {
483 let aabr = shrink_aabr(self.aabr, 6);
484 let center = aabr.center();
485 let alt = land.get_alt_approx(center) as i32;
486
487 painter
488 .aabb(aabr_with_z(aabr, alt..alt + 2))
489 .fill(stone.clone());
490
491 painter
492 .aabb(aabr_with_z(shrink_aabr(aabr, -1), alt..alt + 2))
493 .fill(stone.clone());
494 let iaabr = Aabr {
495 min: aabr.min - 1,
496 max: aabr.max,
497 };
498 painter.benches_around(
499 SpriteKind::BenchWoodWoodland,
500 8,
501 4,
502 shrink_aabr(iaabr, 2),
503 alt + 2,
504 );
505
506 match center_deco {
507 TownCenterDecoration::Gazebo { roof, roof_color } => {
508 paint_gazebo(
509 painter,
510 shrink_aabr(aabr, 3),
511 alt,
512 4,
513 wood.clone(),
514 dark_wood.clone(),
515 roof,
516 roof_color,
517 );
518 },
519 TownCenterDecoration::Tree => {
520 paint_tree(painter, center.with_z(alt));
521 },
522 }
523 painter
524 .aabb(aabr_with_z(shrink_aabr(aabr, 2), alt..alt + 2))
525 .fill(Fill::Block(Block::new(BlockKind::Grass, surface_col)));
526 },
527 PlazaKind::Stage {
528 aabr,
529 facing,
530 closed,
531 roof_color,
532 } => {
533 let roof_fill = Fill::Brick(BlockKind::Wood, roof_color, 8);
534 let height = 5;
535 let alt = land.get_alt_approx(aabr.center()) as i32;
536 let stage_alt = alt + 2;
537 let side = facing.orthogonal();
538 let main_aabr = if closed {
539 facing.opposite().trim_aabr(aabr, 3)
540 } else {
541 aabr
542 };
543 let ground_aabb = painter.aabb(aabr_with_z(aabr, alt + 1..stage_alt));
544 painter
545 .aabb(aabr_with_z(main_aabr, alt - 1..stage_alt))
546 .fill(dark_wood.clone());
547 for corner in aabr_corners(main_aabr) {
548 column(
549 painter,
550 corner.with_z(stage_alt + 0),
551 height,
552 darker_wood.clone(),
553 );
554 }
555 for corner in aabr_corners(shrink_aabr(main_aabr, -1)) {
556 single_block(
557 painter,
558 corner.with_z(stage_alt + height - 1),
559 Block::air(SpriteKind::Lantern),
560 );
561 }
562
563 if closed {
564 let back_corner1 = facing
565 .opposite()
566 .select_aabr_with(main_aabr, side.select_aabr(main_aabr));
567 let back_corner2 = facing
568 .opposite()
569 .select_aabr_with(main_aabr, side.opposite().select_aabr(main_aabr));
570 painter
571 .aabb(
572 Aabb {
573 min: back_corner1.with_z(stage_alt),
574 max: (back_corner2 + facing.scale(1))
575 .with_z(stage_alt + height),
576 }
577 .made_valid(),
578 )
579 .fill(darker_wood.clone());
580 }
581
582 if closed {
583 painter
584 .cylinder(aabr_with_z(aabr, alt + 1..stage_alt))
585 .intersect(ground_aabb)
586 .fill(dark_wood.clone());
587 }
588
589 let aabr_one = shrink_aabr(main_aabr, -1);
591 painter
592 .aabb(aabr_with_z(
593 aabr_one,
594 stage_alt + height..stage_alt + height + 1,
595 ))
596 .fill(roof_fill.clone());
597 let gable_aabr =
598 side.trim_aabr(aabr_one, side.select(main_aabr.half_size() - 2));
599 let gable_aabb =
600 aabr_with_z(gable_aabr, stage_alt + height + 1..stage_alt + height + 4)
601 .made_valid();
602
603 let gable = painter.gable(gable_aabb, 3, facing);
604 let gable_clear = painter.gable(
605 aabr_with_z(
606 Aabr {
607 min: gable_aabr.min + side.scale(1),
608 max: gable_aabr.min + side.scale(gable_aabr.size() - 1)
609 - facing.scale(1),
610 },
611 stage_alt + height..stage_alt + height + 3,
612 ),
613 2,
614 facing,
615 );
616 gable.fill(roof_fill.clone());
617 gable_clear.clear();
618 gable_clear
619 .translate(facing.opposite().scale(main_aabr.size() + 1).with_z(0))
620 .clear();
621 },
622 PlazaKind::Market {
623 center,
624 center_deco,
625 stands,
626 } => {
627 let center_alt = land.get_alt_approx(center.center()) as i32;
628 if let Some(center_deco) = center_deco {
629 match center_deco {
630 TownCenterDecoration::Gazebo { roof, roof_color } => {
631 paint_gazebo(
632 painter,
633 center,
634 center_alt,
635 4,
636 wood.clone(),
637 dark_wood.clone(),
638 roof,
639 roof_color,
640 );
641 },
642 TownCenterDecoration::Tree => {
643 let alt = center_alt;
644 painter
645 .aabb(aabr_with_z(center, alt..alt + 2))
646 .fill(stone.clone());
647 painter
648 .aabb(aabr_with_z(shrink_aabr(center, 1), alt..alt + 2))
649 .fill(Fill::Block(Block::new(
650 BlockKind::Grass,
651 self.park_surface_col,
652 )));
653 paint_tree(painter, center.center().with_z(center_alt + 9));
654 },
655 }
656 }
657
658 for stand in stands {
659 let aabr = stand.aabr;
660 let alt = stand.alt;
661 let facing = stand.facing;
662 let stand_height = 4;
663
664 let roof = Fill::Checker(
665 BlockKind::Wood,
666 stand.roof_color,
667 Rgb::white(),
668 stand.roof_pattern,
669 );
670
671 let counter_fill = Fill::Sampling(std::sync::Arc::new(|center| {
672 match RandomField::new(4973).get(center) % 128 {
673 0..12 => Some(SpriteKind::VialEmpty),
674 12..13 => Some(SpriteKind::PotionMinor),
675 13..24 => Some(SpriteKind::Bowl),
676 24..30 => Some(SpriteKind::Apple),
677 30..32 => Some(SpriteKind::VeloriteFrag),
678 _ => None,
679 }
680 .map(Block::air)
681 }));
682 let counter_dir = stand.facing.rotated_ccw();
683 let corner = counter_dir
684 .opposite()
685 .select_aabr_with(aabr, counter_dir.rotated_cw().select_aabr(aabr));
686
687 let counter = painter.aabb(
688 Aabb {
689 min: corner.with_z(alt + 1),
690 max: (corner
691 + counter_dir.to_vec2() * aabr.size()
692 + counter_dir.rotated_ccw().to_vec2())
693 .with_z(alt + 2),
694 }
695 .made_valid(),
696 );
697 counter.fill(Fill::sprite_ori(
698 SpriteKind::CounterWoodMiddle,
699 counter_dir.sprite_ori(),
700 ));
701 let stock = painter.aabb(
702 Aabb {
703 min: corner.with_z(alt + 2),
704 max: (corner
705 + counter_dir.scale(aabr.size())
706 + counter_dir.rotated_ccw().scale(1))
707 .with_z(alt + 3),
708 }
709 .made_valid(),
710 );
711 stock.fill(counter_fill.clone());
712
713 {
714 let mut corner = aabr.min.with_z(alt + 2);
715 let size = aabr.size();
716 for d in Dir2::ALL {
717 column(
718 painter,
719 corner,
720 stand_height - 2,
721 Fill::Block(Block::air(SpriteKind::FencePost)),
722 );
723
724 column(
725 painter,
726 corner - Vec3::unit_z(),
727 1,
728 Fill::Block(Block::new(BlockKind::Wood, Rgb::new(115, 69, 44))),
729 );
730 corner += d.scale(size - 1);
731 }
732 }
733
734 let c_back_1 = facing
736 .opposite()
737 .select_aabr_with(aabr, facing.rotated_cw().select_aabr(aabr));
738 let c_back_2 = facing
739 .opposite()
740 .select_aabr_with(aabr, facing.rotated_ccw().select_aabr(aabr));
741 let c_front_1 =
742 facing.select_aabr_with(aabr, facing.rotated_cw().select_aabr(aabr));
743 let c_front_2 =
744 facing.select_aabr_with(aabr, facing.rotated_ccw().select_aabr(aabr));
745
746 painter
748 .aabb(
749 Aabb {
750 min: c_back_1.with_z(alt + 1),
751 max: (c_back_2 - facing.opposite().scale(1))
752 .with_z(alt + stand_height + 1),
753 }
754 .made_valid(),
755 )
756 .fill(Fill::Block(Block::new(
757 BlockKind::Wood,
758 Rgb::new(115, 69, 44),
759 )));
760
761 let make_strip = |side: i32, forward: i32, z_off: i32| {
762 let c_front_1 = c_front_1 + facing.rotated_cw().scale(1);
763 let c_front_2 = c_front_2 + facing.rotated_ccw().scale(1);
764 painter
765 .aabb(
766 Aabb {
767 min: (c_front_1 + facing.opposite().scale(forward))
768 .with_z(alt + stand_height + z_off),
769 max: (c_front_1
770 + facing.rotated_ccw().scale(side)
771 + facing.opposite().scale(forward + 1))
772 .with_z(alt + stand_height + z_off + 1),
773 }
774 .made_valid(),
775 )
776 .fill(roof.clone());
777 painter
778 .aabb(
779 Aabb {
780 min: (c_front_2 + facing.opposite().scale(forward))
781 .with_z(alt + stand_height + z_off),
782 max: (c_front_2
783 + facing.rotated_cw().scale(side)
784 + facing.opposite().scale(forward + 1))
785 .with_z(alt + stand_height + z_off + 1),
786 }
787 .made_valid(),
788 )
789 .fill(roof.clone());
790 painter
791 .aabb(
792 Aabb {
793 min: (c_front_1
794 + facing.opposite().scale(forward)
795 + facing.rotated_ccw().scale(side))
796 .with_z(alt + stand_height + z_off + 1),
797 max: (c_front_2
798 + facing.rotated_cw().scale(side)
799 + facing.opposite().scale(forward + 1))
800 .with_z(alt + stand_height + z_off + 2),
801 }
802 .made_valid(),
803 )
804 .fill(roof.clone());
805 };
806
807 let aabr_one = shrink_aabr(aabr, -1);
808 let width = facing.select(aabr_one.size());
809
810 make_strip(3, -1, 0);
811 make_strip(2, 0, 0);
812 make_strip(2, 1, 1);
813 if width == 6 {
814 make_strip(2, 2, 1);
815 make_strip(2, 3, 0);
816 make_strip(3, 4, 0);
817 }
818 if width == 7 {
819 make_strip(1, 2, 1);
820 make_strip(2, 3, 1);
821 make_strip(2, 4, 0);
822 make_strip(3, 5, 0);
823 }
824 if width == 8 {
825 make_strip(1, 2, 1);
826 make_strip(1, 3, 1);
827 make_strip(2, 4, 1);
828 make_strip(2, 5, 0);
829 make_strip(3, 6, 0);
830 }
831 }
832 },
833 }
834 }
835 }
836
837 fn rel_terrain_offset(&self, col: &ColumnSample) -> i32 { col.riverless_alt as i32 }
838
839 fn terrain_surface_at_inner(
840 &self,
841 wpos: Vec2<i32>,
842 old: Block,
843 _rng: &mut ChaCha8Rng,
844 col: &ColumnSample,
845 z_off: i32,
846 _site: &Site,
847 ) -> Option<Block> {
848 let z = self.rel_terrain_offset(col) + z_off;
849 if col.water_level > col.alt || self.hard_alt.is_some_and(|alt| z < alt) {
850 return None;
851 };
852 if z_off <= -1 {
853 let block = self.kind.block(col, wpos.with_z(z), self.dir);
854 if old.is_filled() {
855 if old.is_terrain() { Some(block) } else { None }
856 } else if self.hard_alt.is_none() {
857 Some(block)
858 } else {
859 None
860 }
861 } else if old.is_fluid() || old.kind() == BlockKind::Snow || old.is_terrain() {
862 Some(old.into_vacant())
863 } else {
864 None
865 }
866 }
867}
868
869fn shrink_aabr(aabr: Aabr<i32>, shrink: impl Into<Vec2<i32>>) -> Aabr<i32> {
870 let shrink = shrink.into();
871 Aabr {
872 min: aabr.min + shrink,
873 max: aabr.max - shrink,
874 }
875}
876
877fn column(painter: &Painter, pos: Vec3<i32>, height: i32, fill: Fill) {
878 painter
879 .aabb(Aabb {
880 min: pos,
881 max: pos + Vec2::one() + Vec3::unit_z() * height,
882 })
883 .fill(fill.clone());
884}
885
886fn aabr_corners(aabr: Aabr<i32>) -> [Vec2<i32>; 4] {
887 let size = aabr.size();
888 core::array::from_fn(|i| {
889 let mut corner = aabr.min;
890 for t in 0..i {
891 let dir = Dir2::ALL[t];
892 corner += dir.scale(size - 1);
893 }
894 corner
895 })
896}
897
898fn paint_gazebo(
899 painter: &Painter,
900 aabr: Aabr<i32>,
901 alt: i32,
902 height: i32,
903 wood: Fill,
904 dark_wood: Fill,
905 roof: GazeboRoofKind,
906 roof_color: Rgb<u8>,
907) {
908 painter
909 .aabb(aabr_with_z(aabr, alt..alt + 2))
910 .fill(wood.clone());
911 let aabr = shrink_aabr(aabr, 1);
912 let size = aabr.size();
913 let roof_fill = Fill::Brick(BlockKind::Wood, roof_color, 8);
914 match roof {
915 GazeboRoofKind::Gable => {
916 let gable_height = 3;
917 let gable_inset = 5;
918 let gable_dir = Dir2::from_vec2(size);
919 let gable_aabb = aabr_with_z(
920 shrink_aabr(aabr, -1),
921 alt + height + 3..alt + height + 4 + gable_height,
922 );
923 let gable_aabb_prim = painter.aabb(gable_aabb);
924 let gable = painter.gable(gable_aabb, gable_inset, gable_dir);
925 gable.fill(roof_fill.clone());
926
927 let gable_clear = painter
928 .gable(
929 aabr_with_z(
930 Aabr {
931 min: aabr.min,
932 max: aabr.min + gable_dir.rotated_cw().scale(size) - gable_dir.scale(2),
933 },
934 alt + height + 2..alt + height + 3 + gable_height,
935 ),
936 gable_inset,
937 gable_dir,
938 )
939 .intersect(gable_aabb_prim);
940 gable.translate(Vec3::unit_z() * -2).clear();
942 gable_clear.clear();
943 gable_clear
944 .translate(gable_dir.scale(size + 1).with_z(0))
945 .clear();
946 },
947 GazeboRoofKind::Pyramid => {
948 let pyramid_height = 3;
949 let pyramid_aabb = aabr_with_z(
950 shrink_aabr(aabr, -1),
951 alt + height + 3..alt + height + 4 + pyramid_height,
952 );
953 painter.pyramid(pyramid_aabb).fill(roof_fill.clone());
954 painter
955 .aabb(aabr_with_z(
956 shrink_aabr(aabr, 2),
957 alt + height + 3..alt + height + 4,
958 ))
959 .clear();
960 painter
961 .aabb(aabr_with_z(
962 shrink_aabr(aabr, 3),
963 alt + height + 4..alt + height + 5,
964 ))
965 .clear();
966 },
967 }
968 for corner in aabr_corners(aabr) {
969 column(
970 painter,
971 corner.with_z(alt + 2),
972 height + 1,
973 dark_wood.clone(),
974 );
975 }
976 for corner in aabr_corners(shrink_aabr(aabr, -1)) {
977 single_block(
978 painter,
979 corner.with_z(alt + height + 2),
980 Block::air(SpriteKind::Lantern),
981 );
982 }
983}
984
985fn paint_tree(painter: &Painter, pos: Vec3<i32>) {
986 lazy_static! {
987 static ref FRUIT_TREES: AssetHandle<StructuresGroup> =
988 common::terrain::Structure::load_group("trees.fruit_trees");
989 }
990 let rng = RandomField::new(13579).get(pos) % 10;
991 let fruits = FRUIT_TREES.read();
992 let fruit = fruits[rng as usize % fruits.len()].clone();
993 painter
994 .prim(Primitive::Prefab(Box::new(fruit.clone())))
995 .translate(pos)
996 .fill(Fill::Prefab(Box::new(fruit), pos, rng));
997}