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