veloren_world/site2/plot/
terracotta_palace.rs

1use super::*;
2use crate::{
3    Land,
4    assets::AssetHandle,
5    site2::gen::{PrimitiveTransform, place_circular},
6    util::{DIAGONALS, NEIGHBORS, RandomField, Sampler},
7};
8use common::{
9    generation::EntityInfo,
10    terrain::{BlockKind, SpriteKind, Structure as PrefabStructure, StructuresGroup},
11};
12use lazy_static::lazy_static;
13use rand::prelude::*;
14use std::{f32::consts::TAU, ops::RangeInclusive, sync::Arc};
15use vek::*;
16
17/// Represents house data generated by the `generate()` method
18pub struct TerracottaPalace {
19    /// Axis aligned bounding region for the house
20    bounds: Aabr<i32>,
21    /// Approximate altitude of the door tile
22    pub(crate) alt: i32,
23}
24
25impl TerracottaPalace {
26    pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
27        let bounds = Aabr {
28            min: site.tile_wpos(tile_aabr.min),
29            max: site.tile_wpos(tile_aabr.max),
30        };
31        Self {
32            bounds,
33            alt: land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2))
34                as i32
35                + 2,
36        }
37    }
38
39    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
40        SpawnRules {
41            waypoints: false,
42            trees: wpos.distance_squared(self.bounds.center()) > (85_i32).pow(2),
43            ..SpawnRules::default()
44        }
45    }
46}
47
48impl Structure for TerracottaPalace {
49    #[cfg(feature = "use-dyn-lib")]
50    const UPDATE_FN: &'static [u8] = b"render_terracotta_palace\0";
51
52    #[cfg_attr(feature = "be-dyn-lib", export_name = "render_terracotta_palace")]
53    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
54        let base = self.alt + 1;
55        let center = self.bounds.center();
56        let mut rng = thread_rng();
57        let clay_broken = Fill::Sampling(Arc::new(|center| {
58            Some(match (RandomField::new(0).get(center)) % 42 {
59                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
60                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
61                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
62                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
63                36..=38 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
64                _ => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
65            })
66        }));
67        let clay_broken_2 = Fill::Sampling(Arc::new(|center| {
68            Some(match (RandomField::new(0).get(center)) % 64 {
69                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
70                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
71                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
72                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
73                36..=40 => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
74                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
75            })
76        }));
77        let clay_unbroken = Fill::Sampling(Arc::new(|center| {
78            Some(match (RandomField::new(0).get(center)) % 40 {
79                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
80                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
81                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
82                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
83                _ => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
84            })
85        }));
86        let grass_fill = Fill::Sampling(Arc::new(|wpos| {
87            Some(match (RandomField::new(0).get(wpos)) % 20 {
88                1..=2 => Block::air(SpriteKind::JungleRedGrass),
89                3..=7 => Block::air(SpriteKind::JungleLeafyPlant),
90                8 => Block::air(SpriteKind::JungleFern),
91                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
92            })
93        }));
94        let roof_color = Fill::Sampling(Arc::new(|center| {
95            Some(match (RandomField::new(0).get(center)) % 400 {
96                0..=4 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
97                5..=9 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
98                10..=14 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
99                15..=19 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
100                20..=21 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
101                22..=23 => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
102                _ => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)),
103            })
104        }));
105        let sand = Fill::Brick(BlockKind::Sand, Rgb::new(235, 178, 99), 12);
106        let size = 60;
107        let room_size = 15 * (size / 10);
108        let roof_size = 16 * (size / 10);
109        let carve_size = 14 * (size / 10);
110        let roof_height = room_size / 3;
111        let storeys = 5;
112        let var = size / 5;
113        let clear_var = var / 2;
114        let clear_limit = painter.aabb(Aabb {
115            min: (center - (room_size / 2) - 2).with_z(base),
116            max: (center + (room_size / 2) + 2).with_z(base + (2 * room_size)),
117        });
118        let clear_limit_down = painter.aabb(Aabb {
119            min: (center - (room_size / 2) - 5).with_z(base - (2 * room_size)),
120            max: (center + (room_size / 2) + 5).with_z(base),
121        });
122        let spikes_fill = Fill::Sampling(Arc::new(|center| {
123            Some(match (RandomField::new(0).get(center)) % 8 {
124                0 => Block::air(SpriteKind::IronSpike),
125                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
126            })
127        }));
128        // npcs in upper chamber rooms
129        let mut chamber_npcs = vec![
130            "common.entity.dungeon.terracotta.cursekeeper_fake",
131            "common.entity.dungeon.terracotta.cursekeeper_fake",
132            "common.entity.dungeon.terracotta.cursekeeper_fake",
133            "common.entity.dungeon.terracotta.cursekeeper",
134        ];
135        let mut statue_npcs = vec![
136            "common.entity.dungeon.terracotta.terracotta_statue_key",
137            "common.entity.dungeon.terracotta.terracotta_statue",
138            "common.entity.dungeon.terracotta.terracotta_statue",
139            "common.entity.dungeon.terracotta.terracotta_statue",
140        ];
141        let mut cellar_statue_npcs = vec![
142            "common.entity.dungeon.terracotta.terracotta_statue_key",
143            "common.entity.dungeon.terracotta.terracotta_statue",
144            "common.entity.dungeon.terracotta.terracotta_statue",
145        ];
146        // models
147        let model_radius = (2 * (room_size / 3)) + 6;
148        for dir in NEIGHBORS {
149            let pos = center + dir * model_radius;
150            let palm_model_pos = Vec2::new(pos.x + 2, pos.y);
151            let found_var_a = (RandomField::new(0).get(pos.with_z(base)) % 10) as i32;
152            let found_var_b = (RandomField::new(0).get(pos.with_z(base + 1)) % 10) as i32;
153            let height_var = (found_var_a + found_var_b) / 2;
154            // foundation and decayed wall
155            let foundation_limiter_1 = painter.aabb(Aabb {
156                min: (pos - (room_size / 3) - 1 - found_var_a).with_z(base - room_size),
157                max: (pos + (room_size / 3) + 1 + found_var_b).with_z(base - 1 + height_var),
158            });
159            painter
160                .rounded_aabb(Aabb {
161                    min: (pos - (room_size / 3) - 1 - found_var_a).with_z(base - room_size),
162                    max: (pos + (room_size / 3) + 1 + found_var_b).with_z(base + (room_size / 2)),
163                })
164                .intersect(foundation_limiter_1)
165                .fill(clay_unbroken.clone());
166            let foundation_limiter_2 = painter.aabb(Aabb {
167                min: (pos - (room_size / 3) - 1 - found_var_a).with_z(base - 2),
168                max: (pos + (room_size / 3) + 1 + found_var_b).with_z(base + height_var),
169            });
170            painter
171                .rounded_aabb(Aabb {
172                    min: (pos - (room_size / 3) - 1 - found_var_a).with_z(base - (room_size / 2)),
173                    max: (pos + (room_size / 3) + 1 + found_var_b).with_z(base + (room_size / 2)),
174                })
175                .intersect(foundation_limiter_2)
176                .fill(clay_broken_2.clone());
177            let foundation_limiter_3 = painter.aabb(Aabb {
178                min: (pos - (room_size / 3) - 1 - found_var_a).with_z(base - 2),
179                max: (pos + (room_size / 3) + 1 + found_var_b).with_z(base + height_var + 8),
180            });
181            painter
182                .rounded_aabb(Aabb {
183                    min: (pos - (room_size / 3) + 2 - found_var_a).with_z(base - (room_size / 2)),
184                    max: (pos + (room_size / 3) - 2 + found_var_b).with_z(base + (room_size / 2)),
185                })
186                .intersect(foundation_limiter_3)
187                .clear();
188            let foundation_limiter_4 = painter.aabb(Aabb {
189                min: (pos - (room_size / 3) - found_var_a).with_z(base - 2),
190                max: (pos + (room_size / 3) + found_var_b).with_z(base - 1),
191            });
192            painter
193                .rounded_aabb(Aabb {
194                    min: (pos - (room_size / 3) - found_var_a).with_z(base - (room_size / 2)),
195                    max: (pos + (room_size / 3) + found_var_b).with_z(base + (room_size / 2)),
196                })
197                .intersect(foundation_limiter_4)
198                .fill(sand.clone());
199            // wall decay
200            for dir in NEIGHBORS {
201                let decay_pos = pos + (dir * (room_size / 3));
202                painter
203                    .sphere(Aabb {
204                        min: (decay_pos - 8).with_z(base),
205                        max: (decay_pos + 8).with_z(base + 16),
206                    })
207                    .clear();
208            }
209            // jungle sprites
210            painter
211                .cylinder(Aabb {
212                    min: (pos - 12).with_z(base - 1),
213                    max: (pos + 12).with_z(base),
214                })
215                .fill(grass_fill.clone());
216            // models
217            let model_pos = pos.with_z(base - 5);
218            lazy_static! {
219                pub static ref MODEL: AssetHandle<StructuresGroup> = PrefabStructure::load_group(
220                    "site_structures.terracotta.terracotta_decor_large"
221                );
222            }
223            let rng = RandomField::new(0).get(model_pos) % 62;
224            let model = MODEL.read();
225            let model = model[rng as usize % model.len()].clone();
226            painter
227                .prim(Primitive::Prefab(Box::new(model.clone())))
228                .translate(model_pos)
229                .fill(Fill::Prefab(Box::new(model), model_pos, rng));
230            for dir in DIAGONALS {
231                let model_pos = palm_model_pos + dir * 12;
232                match RandomField::new(0).get(model_pos.with_z(base)) % 4 {
233                    0 => {},
234                    _ => {
235                        for p in 0..2 {
236                            let palm_pos = (model_pos + 3 + (2 * p)).with_z(base - 5);
237                            // no palm tree above exit
238                            let exit = Aabb {
239                                min: (center - (room_size / 2) - 8).with_z(base - 6),
240                                max: (center - (room_size / 2) + 8).with_z(base - 4),
241                            };
242                            if !exit.contains_point(palm_pos) {
243                                lazy_static! {
244                                    pub static ref MODEL: AssetHandle<StructuresGroup> =
245                                        PrefabStructure::load_group("trees.palms");
246                                }
247                                let rng = RandomField::new(0).get(palm_pos) % 62;
248                                let model = MODEL.read();
249                                let model = model[rng as usize % model.len()].clone();
250                                painter
251                                    .prim(Primitive::Prefab(Box::new(model.clone())))
252                                    .translate(palm_pos)
253                                    .fill(Fill::Prefab(Box::new(model), palm_pos, rng));
254                            }
255                        }
256                    },
257                }
258            }
259        }
260        // pavillon
261        let mut pavillons = vec![];
262        let pavillon_dist_x = room_size / 3;
263        let pavillon_dist_y = 2 * (room_size / 3);
264        let pavillon_size_1 = room_size / 8;
265        let pavillon_size_2 = room_size / 10;
266        for dir in DIAGONALS {
267            pavillons.push(Vec2::new(
268                center.x + dir.x * pavillon_dist_x,
269                center.y + dir.y * pavillon_dist_y,
270            ));
271            pavillons.push(Vec2::new(
272                center.x + dir.x * pavillon_dist_y,
273                center.y + dir.y * pavillon_dist_x,
274            ));
275        }
276        for pavillon_pos in pavillons {
277            let entry_carve_limiter = painter.aabb(Aabb {
278                min: (pavillon_pos - pavillon_size_1).with_z(base - pavillon_size_1),
279                max: (pavillon_pos + pavillon_size_1).with_z(base + pavillon_size_1),
280            });
281            let top_carve_limiter = painter.aabb(Aabb {
282                min: (pavillon_pos - pavillon_size_2).with_z(base + pavillon_size_1 - 2),
283                max: (pavillon_pos + pavillon_size_2)
284                    .with_z(base + pavillon_size_1 + pavillon_size_2 - 2),
285            });
286            // base
287            painter
288                .superquadric(
289                    Aabb {
290                        min: (pavillon_pos - pavillon_size_1).with_z(base - pavillon_size_1),
291                        max: (pavillon_pos + pavillon_size_1).with_z(base + pavillon_size_1),
292                    },
293                    2.5,
294                )
295                .fill(clay_broken.clone());
296            // top
297            painter
298                .superquadric(
299                    Aabb {
300                        min: (pavillon_pos - pavillon_size_2).with_z(base + pavillon_size_1 - 2),
301                        max: (pavillon_pos + pavillon_size_2)
302                            .with_z(base + pavillon_size_1 + pavillon_size_2 - 2),
303                    },
304                    2.5,
305                )
306                .fill(clay_broken.clone());
307            painter
308                .superquadric(
309                    Aabb {
310                        min: (pavillon_pos - pavillon_size_2 + 1)
311                            .with_z(base + pavillon_size_1 - 1),
312                        max: (pavillon_pos + pavillon_size_2 - 1)
313                            .with_z(base + pavillon_size_1 + pavillon_size_2 - 3),
314                    },
315                    2.5,
316                )
317                .fill(roof_color.clone());
318            painter
319                .aabb(Aabb {
320                    min: (pavillon_pos - 2).with_z(base + pavillon_size_1 + pavillon_size_2 - 3),
321                    max: (pavillon_pos + 2).with_z(base + pavillon_size_1 + pavillon_size_2 - 2),
322                })
323                .fill(roof_color.clone());
324            painter
325                .aabb(Aabb {
326                    min: (pavillon_pos - 1).with_z(base + pavillon_size_1 + pavillon_size_2 - 2),
327                    max: (pavillon_pos + 1).with_z(base + pavillon_size_1 + pavillon_size_2),
328                })
329                .fill(roof_color.clone());
330            for dir in CARDINALS {
331                let pavillon_carve_pos = pavillon_pos + dir * pavillon_size_2;
332                // entries
333                painter
334                    .superquadric(
335                        Aabb {
336                            min: (pavillon_carve_pos - pavillon_size_2 + 3).with_z(base - 1),
337                            max: (pavillon_carve_pos + pavillon_size_2 - 2)
338                                .with_z(base + pavillon_size_2 - 1),
339                        },
340                        2.5,
341                    )
342                    .intersect(entry_carve_limiter)
343                    .clear();
344                // top carve
345                painter
346                    .superquadric(
347                        Aabb {
348                            min: (pavillon_carve_pos - pavillon_size_2 + 2)
349                                .with_z(base + pavillon_size_1 + 2),
350                            max: (pavillon_carve_pos + pavillon_size_2 - 2)
351                                .with_z(base + pavillon_size_1 + pavillon_size_2 + 2),
352                        },
353                        2.5,
354                    )
355                    .intersect(top_carve_limiter)
356                    .clear();
357            }
358            // center clear
359            painter
360                .cylinder(Aabb {
361                    min: (pavillon_pos - pavillon_size_2 + 2).with_z(base - 1),
362                    max: (pavillon_pos + pavillon_size_2 - 2).with_z(base + 5),
363                })
364                .clear();
365            // solid floor
366            painter
367                .cylinder(Aabb {
368                    min: (pavillon_pos - pavillon_size_2 - 2).with_z(base - 2),
369                    max: (pavillon_pos + pavillon_size_2 + 2).with_z(base - 1),
370                })
371                .fill(clay_unbroken.clone());
372        }
373        // base room
374        painter
375            .superquadric(
376                Aabb {
377                    min: (center - (room_size / 2)).with_z(base - (room_size / 2)),
378                    max: (center + (room_size / 2)).with_z(base + (room_size / 2)),
379                },
380                2.5,
381            )
382            .fill(clay_broken.clone());
383        // solid bottom
384        painter
385            .superquadric(
386                Aabb {
387                    min: (center - (room_size / 2)).with_z(base - (room_size / 2)),
388                    max: (center + (room_size / 2)).with_z(base + (room_size / 2)),
389                },
390                2.5,
391            )
392            .intersect(clear_limit_down)
393            .fill(clay_unbroken.clone());
394        // roof and top rooms
395        for s in 0..storeys {
396            painter
397                .superquadric(
398                    Aabb {
399                        min: (center - (roof_size / 2) + (s * var)).with_z(
400                            base + roof_height - (size / 4) + (s * (roof_size / 4)) + (s * var),
401                        ),
402                        max: (center + (roof_size / 2) - (s * var)).with_z(
403                            base + roof_height - (size / 4) + roof_size + (s * (roof_size / 4))
404                                - (s * var),
405                        ),
406                    },
407                    2.5,
408                )
409                .fill(clay_broken.clone());
410            painter
411                .superquadric(
412                    Aabb {
413                        min: (center - (roof_size / 2) + (s * var) + 1).with_z(
414                            base + roof_height - (size / 4) + (s * (roof_size / 4)) + (s * var) + 1,
415                        ),
416                        max: (center + (roof_size / 2) - (s * var) - 1).with_z(
417                            base + roof_height - (size / 4) + roof_size + (s * (roof_size / 4))
418                                - (s * var)
419                                - 1,
420                        ),
421                    },
422                    2.5,
423                )
424                .fill(roof_color.clone());
425            for dir in CARDINALS {
426                let pos = center + dir * (size - (s * var));
427
428                painter
429                    .superquadric(
430                        Aabb {
431                            min: (pos - (carve_size / 2) + (s * clear_var)).with_z(
432                                base + roof_height + (s * (roof_size / 4)) + (s * clear_var),
433                            ),
434                            max: (pos + (carve_size / 2) - (s * clear_var)).with_z(
435                                base + roof_height + carve_size + (s * (roof_size / 4))
436                                    - (s * clear_var),
437                            ),
438                        },
439                        2.5,
440                    )
441                    .intersect(clear_limit)
442                    .clear();
443            }
444        }
445        // clear base room & entries
446        painter
447            .superquadric(
448                Aabb {
449                    min: (center - (room_size / 2) + 5).with_z(base - (room_size / 2) + 5),
450                    max: (center + (room_size / 2) - 5).with_z(base + (room_size / 2) - 5),
451                },
452                2.5,
453            )
454            .union(
455                painter
456                    .superquadric(
457                        Aabb {
458                            min: Vec2::new(
459                                center.x - (room_size / 4),
460                                center.y - (3 * room_size / 4),
461                            )
462                            .with_z(base - (room_size / 4)),
463                            max: Vec2::new(
464                                center.x + (room_size / 4),
465                                center.y + (3 * room_size / 4),
466                            )
467                            .with_z(base + (room_size / 4)),
468                        },
469                        2.5,
470                    )
471                    .union(
472                        painter.superquadric(
473                            Aabb {
474                                min: Vec2::new(
475                                    center.x - (3 * room_size / 4),
476                                    center.y - (room_size / 4),
477                                )
478                                .with_z(base - (room_size / 4)),
479                                max: Vec2::new(
480                                    center.x + (3 * room_size / 4),
481                                    center.y + (room_size / 4),
482                                )
483                                .with_z(base + (room_size / 4)),
484                            },
485                            2.5,
486                        ),
487                    ),
488            )
489            .intersect(clear_limit)
490            .clear();
491        // clear top
492        painter
493            .superquadric(
494                Aabb {
495                    min: (center - (room_size / 3))
496                        .with_z(base + (3 * (room_size / 10)) - (room_size / 3)),
497                    max: (center + (room_size / 3))
498                        .with_z(base + (3 * (room_size / 10)) + (room_size / 4)),
499                },
500                2.5,
501            )
502            .clear();
503        painter
504            .superquadric(
505                Aabb {
506                    min: (center - (room_size / 4))
507                        .with_z(base + (3 * (room_size / 10)) - (room_size / 4)),
508                    max: (center + (room_size / 4))
509                        .with_z(base + (3 * (room_size / 10)) + (room_size / 3)),
510                },
511                2.5,
512            )
513            .clear();
514        // top floor with center clearing
515        painter
516            .cylinder(Aabb {
517                min: (center - (room_size / 2) + 8).with_z(base + (3 * (room_size / 10)) - 1),
518                max: (center + (room_size / 2) - 8).with_z(base + (3 * (room_size / 10)) + 2),
519            })
520            .fill(clay_unbroken.clone());
521        painter
522            .cylinder(Aabb {
523                min: (center - (room_size / 4) + 1).with_z(base + (3 * (room_size / 10)) - 1),
524                max: (center + (room_size / 4) - 1).with_z(base + (3 * (room_size / 10)) + 2),
525            })
526            .clear();
527        // center podium with spikes
528        painter
529            .cylinder(Aabb {
530                min: (center - (room_size / 4) + 1).with_z(base + (3 * (room_size / 10)) + 1),
531                max: (center + (room_size / 4) - 1).with_z(base + (3 * (room_size / 10)) + 2),
532            })
533            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
534        painter
535            .cylinder(Aabb {
536                min: (center - (room_size / 4) - 1).with_z(base + (2 * (room_size / 10)) + 3),
537                max: (center + (room_size / 4) + 1).with_z(base + (2 * (room_size / 10)) + 4),
538            })
539            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
540
541        for chain_pos in place_circular(center, ((room_size / 4) + 1) as f32, 20) {
542            painter
543                .aabb(Aabb {
544                    min: (chain_pos - 1).with_z(base + (2 * (room_size / 10)) + 3),
545                    max: chain_pos.with_z(base + (3 * (room_size / 10)) - 1),
546                })
547                .fill(Fill::Block(Block::air(SpriteKind::MetalChain)));
548            painter
549                .cylinder(Aabb {
550                    min: (chain_pos - 3).with_z(base + (2 * (room_size / 10)) + 2),
551                    max: (chain_pos + 1).with_z(base + (2 * (room_size / 10)) + 3),
552                })
553                .fill(roof_color.clone());
554        }
555        painter
556            .cylinder(Aabb {
557                min: (center - (room_size / 8) - 5).with_z(base - 1),
558                max: (center + (room_size / 8) + 5).with_z(base),
559            })
560            .fill(clay_unbroken.clone());
561        painter
562            .cylinder(Aabb {
563                min: (center - (room_size / 8) - 5).with_z(base),
564                max: (center + (room_size / 8) + 5).with_z(base + 1),
565            })
566            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
567        painter
568            .cylinder(Aabb {
569                min: (center - (room_size / 8) - 3).with_z(base + 1),
570                max: (center + (room_size / 8) + 3).with_z(base + (3 * (room_size / 10)) + 2),
571            })
572            .fill(spikes_fill);
573        painter
574            .cylinder(Aabb {
575                min: (center - (room_size / 8) - 2).with_z(base - 20),
576                max: (center + (room_size / 8) + 2).with_z(base + (3 * (room_size / 10)) + 2),
577            })
578            .fill(clay_unbroken.clone());
579        painter
580            .cylinder(Aabb {
581                min: (center - (room_size / 8)).with_z(base + (3 * (room_size / 10)) + 1),
582                max: (center + (room_size / 8)).with_z(base + (3 * (room_size / 10)) + 2),
583            })
584            .fill(roof_color.clone());
585        painter
586            .cylinder(Aabb {
587                min: (center - (room_size / 8)).with_z(base - 10),
588                max: (center + (room_size / 8)).with_z(base - 15),
589            })
590            .fill(roof_color.clone());
591        // cellar
592        painter
593            .superquadric(
594                Aabb {
595                    min: (center - (room_size / 2)).with_z(base - 10 - room_size),
596                    max: (center + (room_size / 2)).with_z(base - 10),
597                },
598                2.5,
599            )
600            .fill(clay_unbroken.clone());
601        // cellar chambers
602        painter
603            .line(
604                Vec2::new(center.x - (room_size / 2) - 8, center.y)
605                    .with_z(base - 10 - (room_size / 2)),
606                Vec2::new(center.x + (room_size / 2) + 8, center.y)
607                    .with_z(base - 10 - (room_size / 2)),
608                8.0,
609            )
610            .fill(clay_unbroken.clone());
611        painter
612            .line(
613                Vec2::new(center.x, center.y - (room_size / 2) - 8)
614                    .with_z(base - 10 - (room_size / 2)),
615                Vec2::new(center.x, center.y + (room_size / 2) + 8)
616                    .with_z(base - 10 - (room_size / 2)),
617                8.0,
618            )
619            .fill(clay_unbroken.clone());
620        painter
621            .line(
622                Vec2::new(center.x - (room_size / 2) - 8, center.y)
623                    .with_z(base - 10 - (room_size / 2)),
624                Vec2::new(center.x + (room_size / 2) + 8, center.y)
625                    .with_z(base - 10 - (room_size / 2)),
626                5.0,
627            )
628            .clear();
629        painter
630            .line(
631                Vec2::new(center.x, center.y - (room_size / 2) - 8)
632                    .with_z(base - 10 - (room_size / 2)),
633                Vec2::new(center.x, center.y + (room_size / 2) + 8)
634                    .with_z(base - 10 - (room_size / 2)),
635                5.0,
636            )
637            .clear();
638        // chamber rooms
639        for dir in CARDINALS {
640            let room_center = center + dir * ((room_size / 2) + 8);
641            painter
642                .cylinder(Aabb {
643                    min: (room_center - 3).with_z(base - 30 - (room_size / 2) - (room_size / 16)),
644                    max: (room_center + 3).with_z(base - 6 - (room_size / 2) - (room_size / 16)),
645                })
646                .clear();
647            // cursekeeper in chamber rooms
648            let npc_pos = room_center.with_z(base - 24 - (room_size / 2) - (room_size / 16));
649            let npc = chamber_npcs
650                .swap_remove(RandomField::new(0).get(npc_pos) as usize % chamber_npcs.len());
651
652            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(npc, &mut rng, None));
653        }
654        // chamber_1
655        painter
656            .aabb(Aabb {
657                min: Vec2::new(center.x - (room_size / 2), center.y - 4)
658                    .with_z(base - 10 - (room_size / 2)),
659                max: Vec2::new(center.x - (room_size / 2) + 1, center.y + 5)
660                    .with_z(base - 10 - (room_size / 2) + 5),
661            })
662            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyDoor)));
663        painter
664            .aabb(Aabb {
665                min: Vec2::new(center.x - (room_size / 2), center.y)
666                    .with_z(base - 10 - (room_size / 2) + 1),
667                max: Vec2::new(center.x - (room_size / 2) + 1, center.y + 1)
668                    .with_z(base - 10 - (room_size / 2) + 2),
669            })
670            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyhole)));
671        // chamber_2
672        painter
673            .aabb(Aabb {
674                min: Vec2::new(center.x + (room_size / 2) - 1, center.y - 4)
675                    .with_z(base - 10 - (room_size / 2)),
676                max: Vec2::new(center.x + (room_size / 2), center.y + 5)
677                    .with_z(base - 10 - (room_size / 2) + 5),
678            })
679            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyDoor)));
680        painter
681            .aabb(Aabb {
682                min: Vec2::new(center.x + (room_size / 2) - 1, center.y)
683                    .with_z(base - 10 - (room_size / 2) + 1),
684                max: Vec2::new(center.x + (room_size / 2), center.y + 1)
685                    .with_z(base - 10 - (room_size / 2) + 2),
686            })
687            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyhole)));
688        // chamber_3
689        painter
690            .aabb(Aabb {
691                min: Vec2::new(center.x - 4, center.y - (room_size / 2))
692                    .with_z(base - 10 - (room_size / 2)),
693                max: Vec2::new(center.x + 5, center.y - (room_size / 2) + 1)
694                    .with_z(base - 10 - (room_size / 2) + 5),
695            })
696            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyDoor)));
697        painter
698            .aabb(Aabb {
699                min: Vec2::new(center.x, center.y - (room_size / 2))
700                    .with_z(base - 10 - (room_size / 2) + 1),
701                max: Vec2::new(center.x + 1, center.y - (room_size / 2) + 1)
702                    .with_z(base - 10 - (room_size / 2) + 2),
703            })
704            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyhole)));
705        // chamber_4
706        painter
707            .aabb(Aabb {
708                min: Vec2::new(center.x - 4, center.y + (room_size / 2) - 1)
709                    .with_z(base - 10 - (room_size / 2)),
710                max: Vec2::new(center.x + 5, center.y + (room_size / 2))
711                    .with_z(base - 10 - (room_size / 2) + 5),
712            })
713            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyDoor)));
714        painter
715            .aabb(Aabb {
716                min: Vec2::new(center.x, center.y + (room_size / 2) - 1)
717                    .with_z(base - 10 - (room_size / 2) + 1),
718                max: Vec2::new(center.x + 1, center.y + (room_size / 2))
719                    .with_z(base - 10 - (room_size / 2) + 2),
720            })
721            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyhole)));
722        // clear cellar
723        painter
724            .superquadric(
725                Aabb {
726                    min: (center - (room_size / 2) + 1).with_z(base - 10 - room_size + 1),
727                    max: (center + (room_size / 2) - 1).with_z(base - 10 - 1),
728                },
729                2.5,
730            )
731            .clear();
732        // cellar exit tunnel to surface
733        // hide tunnel to prevent climbing on top to cheese
734        let sq_limit = painter.aabb(Aabb {
735            min: (center - (room_size / 4) - 11).with_z(base - 20 - (room_size / 2)),
736            max: (center - (room_size / 4) + 16).with_z(base - 1),
737        });
738        painter
739            .superquadric(
740                Aabb {
741                    min: (center - (room_size / 4) - 9).with_z(base - 20 - (room_size / 2)),
742                    max: (center - (room_size / 4) + 16).with_z(base + 20),
743                },
744                3.5,
745            )
746            .intersect(sq_limit)
747            .fill(clay_unbroken.clone());
748        let cyl_1_pos = Vec2::new(
749            center.x - (room_size / 4) - 2,
750            center.y - (room_size / 4) - 13,
751        );
752        let cyl_2_pos = Vec2::new(
753            center.x - (room_size / 4) - 12,
754            center.y - (room_size / 4) - 5,
755        );
756        painter
757            .aabb(Aabb {
758                min: (cyl_1_pos - 15).with_z(base - 10 - (room_size / 2)),
759                max: (cyl_1_pos + 15).with_z(base - 10),
760            })
761            .fill(clay_unbroken.clone());
762
763        painter
764            .aabb(Aabb {
765                min: (cyl_2_pos - 15).with_z(base - 10 - (room_size / 2)),
766                max: (cyl_2_pos + 15).with_z(base - 10),
767            })
768            .fill(clay_unbroken.clone());
769        // tunnel
770        painter
771            .line(
772                (center - (room_size / 4)).with_z(base - 10 - (room_size / 2)),
773                (center - (room_size / 2)).with_z(base - 5),
774                10.0,
775            )
776            .fill(clay_unbroken.clone());
777        // exit
778        let exit_pos = Vec2::new(
779            center.x - (room_size / 4) + 9,
780            center.y + 4 - (room_size / 4),
781        );
782        painter
783            .cylinder(Aabb {
784                min: (exit_pos - 4).with_z(base - 10 - (room_size / 2)),
785                max: (exit_pos + 5).with_z(base + 4 - (room_size / 2)),
786            })
787            .fill(clay_unbroken.clone());
788        painter
789            .aabb(Aabb {
790                min: Vec2::new(exit_pos.x + 5, exit_pos.y - 2).with_z(base - 10 - (room_size / 2)),
791                max: Vec2::new(exit_pos.x + 6, exit_pos.y + 3).with_z(base + 25 - (room_size / 2)),
792            })
793            .fill(clay_unbroken.clone());
794        painter
795            .aabb(Aabb {
796                min: Vec2::new(exit_pos.x + 5, exit_pos.y - 1).with_z(base - 10 - (room_size / 2)),
797                max: Vec2::new(exit_pos.x + 6, exit_pos.y + 2).with_z(base + 6 - (room_size / 2)),
798            })
799            .clear();
800        painter
801            .cylinder(Aabb {
802                min: (exit_pos - 3).with_z(base - 10 - (room_size / 2)),
803                max: (exit_pos + 4).with_z(base + 10 - (room_size / 2)),
804            })
805            .clear();
806        painter
807            .aabb(Aabb {
808                min: Vec2::new(exit_pos.x + 4, exit_pos.y - 1).with_z(base + 3 - (room_size / 2)),
809                max: Vec2::new(exit_pos.x + 5, exit_pos.y + 2).with_z(base + 6 - (room_size / 2)),
810            })
811            .clear();
812        painter
813            .aabb(Aabb {
814                min: Vec2::new(exit_pos.x + 4, exit_pos.y - 1).with_z(base + 2 - (room_size / 2)),
815                max: Vec2::new(exit_pos.x + 5, exit_pos.y + 2).with_z(base + 3 - (room_size / 2)),
816            })
817            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
818        painter
819            .aabb(Aabb {
820                min: Vec2::new(exit_pos.x + 5, exit_pos.y - 1).with_z(base - 10 - (room_size / 2)),
821                max: Vec2::new(exit_pos.x + 6, exit_pos.y + 2).with_z(base + 6 - (room_size / 2)),
822            })
823            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyDoor)));
824        painter
825            .aabb(Aabb {
826                min: Vec2::new(exit_pos.x + 5, exit_pos.y).with_z(base - 9 - (room_size / 2)),
827                max: Vec2::new(exit_pos.x + 6, exit_pos.y + 1).with_z(base - 8 - (room_size / 2)),
828            })
829            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyhole)));
830        // chests
831        let chests_position = exit_pos + 8;
832        painter
833            .cylinder(Aabb {
834                min: (chests_position - 4).with_z(base - 10 - (room_size / 2)),
835                max: (chests_position + 5).with_z(base - 9 - (room_size / 2)),
836            })
837            .fill(roof_color.clone());
838        painter
839            .cylinder(Aabb {
840                min: (chests_position - 3).with_z(base - 10 - (room_size / 2)),
841                max: (chests_position + 4).with_z(base - 9 - (room_size / 2)),
842            })
843            .fill(clay_unbroken.clone());
844        for dir in CARDINALS {
845            let chest_pos = chests_position + (dir * 2);
846            painter.sprite(
847                chest_pos.with_z(base - 9 - (room_size / 2)),
848                SpriteKind::TerracottaChest,
849            );
850        }
851        // npcs
852        // outside statues and npcs on pillars
853        for dir in DIAGONALS {
854            let pos = center + dir * ((size / 2) + 6);
855            painter
856                .cylinder(Aabb {
857                    min: (pos - 3).with_z(base - (room_size / 2)),
858                    max: (pos + 3).with_z(base + 3),
859                })
860                .fill(clay_broken.clone());
861            painter
862                .cylinder(Aabb {
863                    min: (pos - 4).with_z(base + 3),
864                    max: (pos + 4).with_z(base + 4),
865                })
866                .fill(clay_broken_2.clone());
867            painter
868                .cylinder(Aabb {
869                    min: (pos - 5).with_z(base + 4),
870                    max: (pos + 5).with_z(base + 5),
871                })
872                .fill(clay_broken_2.clone());
873            painter
874                .cylinder(Aabb {
875                    min: (pos - 6).with_z(base + 5),
876                    max: (pos + 6).with_z(base + 6),
877                })
878                .fill(clay_broken.clone());
879            painter
880                .cylinder(Aabb {
881                    min: (pos - 4).with_z(base + 5),
882                    max: (pos + 4).with_z(base + 6),
883                })
884                .fill(Fill::Block(Block::air(SpriteKind::TerracottaBlock)));
885            painter
886                .cylinder(Aabb {
887                    min: (pos - 4).with_z(base + 4),
888                    max: (pos + 4).with_z(base + 5),
889                })
890                .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
891            painter
892                .cylinder(Aabb {
893                    min: (pos - 1).with_z(base + 4),
894                    max: (pos + 1).with_z(base + 6),
895                })
896                .fill(clay_broken.clone());
897
898            let pillar_npc_pos = (center + dir * ((size / 2) + 9)).with_z(base + 6);
899            let statue_pos = (center + dir * ((size / 2) + 5)).with_z(base + 6);
900
901            spawn_random_entity(pillar_npc_pos, painter, 1..=1);
902
903            let statue = statue_npcs
904                .swap_remove(RandomField::new(0).get(statue_pos) as usize % statue_npcs.len());
905            painter.spawn(
906                EntityInfo::at((statue_pos).as_()).with_asset_expect(statue, &mut rng, None),
907            );
908        }
909        // clear tunnel
910        painter
911            .line(
912                (center - (room_size / 4)).with_z(base - 10 - (room_size / 2)),
913                (center - (room_size / 2)).with_z(base),
914                7.0,
915            )
916            .clear();
917        painter
918            .cylinder(Aabb {
919                min: (center - (room_size / 2) - 10).with_z(base),
920                max: (center - (room_size / 2) + 10).with_z(base + 10),
921            })
922            .clear();
923        // cellar entry from podium top
924        painter
925            .cylinder(Aabb {
926                min: (center - (room_size / 8) + 3).with_z(base - 20),
927                max: (center + (room_size / 8) - 3).with_z(base + (3 * (room_size / 10)) + 2),
928            })
929            .clear();
930        painter
931            .cylinder(Aabb {
932                min: (center - (room_size / 8) + 3).with_z(base + (3 * (room_size / 10)) + 1),
933                max: (center + (room_size / 8) - 3).with_z(base + (3 * (room_size / 10)) + 2),
934            })
935            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
936        painter
937            .cylinder(Aabb {
938                min: (center - 2).with_z(base + (3 * (room_size / 10)) + 1),
939                max: (center + 2).with_z(base + (3 * (room_size / 10)) + 2),
940            })
941            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyDoor)));
942        painter
943            .cylinder(Aabb {
944                min: (center).with_z(base + (3 * (room_size / 10)) + 1),
945                max: (center + 1).with_z(base + (3 * (room_size / 10)) + 2),
946            })
947            .fill(Fill::Block(Block::air(SpriteKind::TerracottaKeyhole)));
948        // cellar floor
949        painter
950            .aabb(Aabb {
951                min: (center - (room_size / 2)).with_z(base - 10 - room_size),
952                max: (center + (room_size / 2)).with_z(base - 10 - (room_size / 2)),
953            })
954            .fill(clay_unbroken.clone());
955        // sprites
956        // top room lamps and chests
957        let lamps_top = 6.0_f32;
958        let radius_lamps_top = (room_size / 4) + 3;
959        let phi_lamps_top = TAU / lamps_top;
960        for n in 1..=lamps_top as i32 {
961            let pos = Vec2::new(
962                center.x + (radius_lamps_top as f32 * ((n as f32 * phi_lamps_top).cos())) as i32,
963                center.y + (radius_lamps_top as f32 * ((n as f32 * phi_lamps_top).sin())) as i32,
964            );
965            painter.sprite(
966                pos.with_z(base + (3 * (room_size / 10)) + 2),
967                SpriteKind::TerracottaStatue,
968            );
969        }
970        let chests = 4.0_f32;
971        let radius_chests = (room_size / 4) + 6;
972        let phi_chests = TAU / chests;
973        for n in 1..=chests as i32 {
974            let pos = Vec2::new(
975                center.x + (radius_chests as f32 * ((n as f32 * phi_chests).cos())) as i32,
976                center.y + (radius_chests as f32 * ((n as f32 * phi_chests).sin())) as i32,
977            );
978            painter.sprite(
979                pos.with_z(base + (3 * (room_size / 10)) + 2),
980                SpriteKind::TerracottaChest,
981            );
982        }
983        // main room lamps
984        let radius_lamps_main = (room_size / 4) + 12;
985        let lamps_main = 8.0_f32;
986        let phi_lamps_main = TAU / lamps_main;
987        for n in 1..=lamps_main as i32 {
988            let pos = Vec2::new(
989                center.x + (radius_lamps_main as f32 * ((n as f32 * phi_lamps_main).cos())) as i32,
990                center.y + (radius_lamps_main as f32 * ((n as f32 * phi_lamps_main).sin())) as i32,
991            );
992            painter.sprite(pos.with_z(base), SpriteKind::TerracottaStatue);
993        }
994        // cellar statues
995        let radius_statues_cellar = (room_size / 2) - 12;
996        let statues_cellar = 11.0_f32;
997        let phi_statues_cellar = TAU / statues_cellar;
998        for n in 1..=statues_cellar as i32 {
999            let pos = Vec2::new(
1000                center.x
1001                    + (radius_statues_cellar as f32 * ((n as f32 * phi_statues_cellar).cos()))
1002                        as i32,
1003                center.y
1004                    + (radius_statues_cellar as f32 * ((n as f32 * phi_statues_cellar).sin()))
1005                        as i32,
1006            );
1007            match n {
1008                1 | 5 | 9 => {
1009                    painter
1010                        .cylinder(Aabb {
1011                            min: (pos - 8).with_z(base - 22 - (room_size / 2)),
1012                            max: (pos + 8).with_z(base - 10 - (room_size / 2)),
1013                        })
1014                        .clear();
1015                    painter
1016                        .cylinder(Aabb {
1017                            min: (pos - 8).with_z(base - 22 - (room_size / 2)),
1018                            max: (pos + 8).with_z(base - 21 - (room_size / 2)),
1019                        })
1020                        .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
1021                    painter
1022                        .cylinder(Aabb {
1023                            min: (pos - 8).with_z(base - 11 - (room_size / 2)),
1024                            max: (pos + 8).with_z(base - 10 - (room_size / 2)),
1025                        })
1026                        .fill(Fill::Block(Block::air(SpriteKind::TerracottaBlock)));
1027                    painter
1028                        .cylinder(Aabb {
1029                            min: (pos).with_z(base - 22 - (room_size / 2)),
1030                            max: (pos + 1).with_z(base - 10 - (room_size / 2)),
1031                        })
1032                        .fill(clay_unbroken.clone());
1033                    let cellar_statue_pos = pos.with_z(base - 10 - (room_size / 2));
1034                    let cellar_statue = cellar_statue_npcs.swap_remove(
1035                        RandomField::new(0).get(cellar_statue_pos) as usize
1036                            % cellar_statue_npcs.len(),
1037                    );
1038                    painter.spawn(EntityInfo::at(cellar_statue_pos.as_()).with_asset_expect(
1039                        cellar_statue,
1040                        &mut rng,
1041                        None,
1042                    ));
1043                },
1044                _ => {
1045                    painter.sprite(
1046                        pos.with_z(base - 10 - (room_size / 2)),
1047                        SpriteKind::TerracottaStatue,
1048                    );
1049                },
1050            }
1051        }
1052        // npcs on top floor
1053        let npcs = 5.0_f32;
1054        let radius_npcs = (room_size / 4) + 6;
1055        let phi_npcs = TAU / npcs;
1056        for n in 1..=npcs as i32 {
1057            let pos = Vec2::new(
1058                center.x + (radius_npcs as f32 * ((n as f32 * phi_npcs).cos())) as i32,
1059                center.y + (radius_npcs as f32 * ((n as f32 * phi_npcs).sin())) as i32,
1060            )
1061            .with_z(base + (3 * (room_size / 10)) + 2);
1062            spawn_random_entity(pos, painter, 1..=1);
1063        }
1064        // platform mogwai
1065        painter.spawn(
1066            EntityInfo::at((center.with_z(base + (3 * (room_size / 10)) + 2)).as_())
1067                .with_asset_expect("common.entity.dungeon.terracotta.mogwai", &mut rng, None),
1068        );
1069
1070        // main room npcs
1071        let radius_npcs_main = (room_size / 4) + 10;
1072        let npcs_main = 15.0_f32;
1073        let phi_npcs_main = TAU / npcs_main;
1074        for n in 1..=npcs_main as i32 {
1075            let pos = Vec2::new(
1076                center.x + (radius_npcs_main as f32 * ((n as f32 * phi_npcs_main).cos())) as i32,
1077                center.y + (radius_npcs_main as f32 * ((n as f32 * phi_npcs_main).sin())) as i32,
1078            )
1079            .with_z(base);
1080            spawn_random_entity(pos, painter, 1..=1);
1081        }
1082    }
1083}
1084
1085pub fn spawn_random_entity(pos: Vec3<i32>, painter: &Painter, amount: RangeInclusive<i32>) {
1086    let mut rng = thread_rng();
1087    let entities = [
1088        "common.entity.dungeon.terracotta.besieger",
1089        "common.entity.dungeon.terracotta.demolisher",
1090        "common.entity.dungeon.terracotta.punisher",
1091        "common.entity.dungeon.terracotta.pursuer",
1092        "common.entity.dungeon.terracotta.shamanic_spirit",
1093        "common.entity.dungeon.terracotta.jiangshi",
1094    ];
1095    for n in amount {
1096        let random_entity_index = rng.gen_range(0..entities.len());
1097        let random_entity = entities[random_entity_index];
1098        let position = Vec3::new(pos.x + n, pos.y + n, pos.z);
1099        painter.spawn(EntityInfo::at(position.as_()).with_asset_expect(
1100            random_entity,
1101            &mut rng,
1102            None,
1103        ));
1104    }
1105}