Skip to main content

veloren_world/site/plot/
terracotta_palace.rs

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