Skip to main content

veloren_world/site/plot/
haniwa.rs

1use super::*;
2use crate::{
3    Land,
4    assets::AssetHandle,
5    site::generation::{PrimitiveTransform, place_circular, place_circular_as_vec},
6    util::{DIAGONALS, LOCALITY, NEIGHBORS, RandomField, sampler::Sampler, within_distance},
7};
8use common::{
9    generation::EntityInfo,
10    terrain::{Structure as PrefabStructure, StructuresGroup},
11};
12use lazy_static::lazy_static;
13use rand::prelude::*;
14use std::{f32::consts::PI, sync::Arc};
15use vek::*;
16
17pub struct Haniwa {
18    base: i32,
19    diameter: i32,
20    tree_pos: Vec3<i32>,
21    room_size: i32,
22    rotation: f32,
23    pub(crate) alt: i32,
24    pub(crate) center: Vec2<i32>,
25    pub(crate) entrance_pos: Vec3<i32>,
26    pub(crate) mob_room_positions: Vec<Vec3<i32>>,
27    pub(crate) center_room_positions: Vec<Vec3<i32>>,
28    pub(crate) room_positions: Vec<Vec3<i32>>,
29    pub(crate) boss_room_position: Vec3<i32>,
30    pub(crate) mini_boss_room_positions: Vec<Vec3<i32>>,
31}
32impl Haniwa {
33    pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
34        let bounds = Aabr {
35            min: site.tile_wpos(tile_aabr.min),
36            max: site.tile_wpos(tile_aabr.max),
37        };
38        let center = bounds.center();
39        let base = land.get_alt_approx(center) as i32;
40        let diameter = (150 + RandomField::new(0).get(center.with_z(base)) % 10) as i32;
41        let dir_select = (RandomField::new(0).get(center.with_z(base)) % 4) as usize;
42        let rotation = (PI / 2.0) * dir_select as f32;
43        let entrance_dir = CARDINALS[dir_select];
44        let tree_dir = -entrance_dir;
45        let entrance_pos = (center + (entrance_dir * (2 * (diameter / 3)))).with_z(base);
46        let tree_distance = diameter / 2;
47        let tree_pos = (center + (tree_dir * tree_distance)).with_z(base);
48        let room_size = diameter / 4;
49        let mut floors = vec![];
50        for f in 1..=3 {
51            let floor = base - ((diameter / 5) * f) - 2;
52            floors.push(floor)
53        }
54        let mut mob_room_positions = vec![];
55        let mut mini_boss_room_positions = vec![];
56        let mut center_room_positions = vec![];
57        let mut room_positions = vec![];
58        for floor in floors.iter().take(floors.len() - 1) {
59            let (room_distribution, room_distance) =
60                if RandomField::new(0).get(center.with_z(*floor)) % 2 > 0 {
61                    (DIAGONALS, (3 * (room_size / 2)) + 2)
62                } else {
63                    (CARDINALS, 2 * (room_size - 2))
64                };
65            for dir in room_distribution {
66                let room_center = center + dir * room_distance;
67                mob_room_positions.push(room_center.with_z(floor - (room_size / 4) + 1));
68                room_positions.push(room_center.with_z(floor - (room_size / 4) + 1));
69            }
70            mini_boss_room_positions.push(center.with_z(floor - (room_size / 4) + 1));
71        }
72        for floor in &floors {
73            center_room_positions.push(center.with_z(floor - (room_size / 4) + 1));
74            room_positions.push(center.with_z(floor - (room_size / 4) + 1));
75        }
76        let boss_room_position = center_room_positions[center_room_positions.len() - 1];
77        Self {
78            alt: land.get_alt_approx(site.tile_center_wpos(tile_aabr.center())) as i32 + 2,
79            center,
80            base,
81            diameter,
82            entrance_pos,
83            tree_pos,
84            room_size,
85            rotation,
86            room_positions,
87            mob_room_positions,
88            center_room_positions,
89            boss_room_position,
90            mini_boss_room_positions,
91        }
92    }
93}
94
95impl Structure for Haniwa {
96    #[cfg(feature = "dyn-lib")]
97    #[unsafe(export_name = "as_dyn_structure_haniwa")]
98    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
99        Some((Self::as_dyn_impl(self), "as_dyn_structure_haniwa"))
100    }
101
102    fn spawn_rules_inner(
103        &self,
104        spawn_rules: &mut SpawnRules,
105        _land: &Land,
106        wpos: Vec2<i32>,
107        _weight: f32,
108    ) {
109        spawn_rules.trees &= !within_distance(wpos, self.center, self.diameter);
110        spawn_rules.waypoints = false;
111    }
112
113    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
114        let center = self.center;
115        let base = self.base;
116        let diameter = self.diameter;
117        let entrance = self.entrance_pos;
118        let mut rng = rand::rng();
119        let rock = Fill::Brick(BlockKind::Rock, Rgb::new(96, 123, 131), 24);
120        let key_door = Fill::Block(Block::air(SpriteKind::HaniwaKeyDoor));
121        let key_hole = Fill::Block(Block::air(SpriteKind::HaniwaKeyhole));
122        let trap = Fill::Block(Block::air(SpriteKind::HaniwaTrap));
123        let rock_broken = Fill::Sampling(Arc::new(|center| {
124            Some(match (RandomField::new(0).get(center)) % 48 {
125                0..=8 => Block::new(BlockKind::Rock, Rgb::new(97, 124, 134)),
126                9..=17 => Block::new(BlockKind::Rock, Rgb::new(92, 118, 128)),
127                18..=26 => Block::new(BlockKind::Rock, Rgb::new(85, 111, 121)),
128                27..=35 => Block::new(BlockKind::Rock, Rgb::new(82, 108, 117)),
129                36..=40 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
130                _ => Block::new(BlockKind::Rock, Rgb::new(96, 123, 131)),
131            })
132        }));
133        let lanterns = Fill::Sampling(Arc::new(|center| {
134            Some(match (RandomField::new(0).get(center)) % 200 {
135                0 => Block::air(SpriteKind::FireBowlGround),
136                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
137            })
138        }));
139        let iron_spikes = Fill::Block(Block::air(SpriteKind::IronSpike));
140        let rock_iron_spikes = Fill::Sampling(Arc::new(|center| {
141            Some(match (RandomField::new(0).get(center)) % 100 {
142                0..=8 => Block::new(BlockKind::Rock, Rgb::new(97, 124, 134)),
143                9..=17 => Block::new(BlockKind::Rock, Rgb::new(92, 118, 128)),
144                18..=26 => Block::new(BlockKind::Rock, Rgb::new(85, 111, 121)),
145                27..=35 => Block::new(BlockKind::Rock, Rgb::new(82, 108, 117)),
146                36..=40 => Block::new(BlockKind::Rock, Rgb::new(96, 123, 131)),
147                _ => Block::air(SpriteKind::IronSpike),
148            })
149        }));
150        let grass = Fill::Brick(BlockKind::Rock, Rgb::new(72, 87, 22), 24);
151        // room npcs
152        let npcs = [
153            "common.entity.dungeon.haniwa.guard",
154            "common.entity.dungeon.haniwa.soldier",
155        ];
156        let height_handle = diameter / 8;
157        let cone_length = 8;
158        let cone_radius = (diameter / 2) as f32;
159        let cones = diameter / 4;
160        let cone_positions = place_circular(center, cone_radius, cones);
161        // tree platform
162        let outside_radius = cone_radius as i32;
163        let tree_pos = Vec2::new(self.tree_pos.x, self.tree_pos.y);
164        let platform_size = 23;
165        painter
166            .cylinder(Aabb {
167                min: (tree_pos - platform_size).with_z(base - 5),
168                max: (tree_pos + platform_size).with_z(base - 10 + platform_size - 1),
169            })
170            .fill(rock_broken.clone());
171        painter
172            .cylinder(Aabb {
173                min: (tree_pos - platform_size).with_z(base - 10 + platform_size - 1),
174                max: (tree_pos + platform_size).with_z(base - 10 + platform_size),
175            })
176            .fill(grass.clone());
177        let carve_positions = place_circular(tree_pos, platform_size as f32, 15);
178        for carve_pos in carve_positions {
179            painter
180                .line(
181                    carve_pos.with_z(base + 2),
182                    carve_pos.with_z(base - 5 + platform_size),
183                    3.5,
184                )
185                .clear();
186        }
187        // gangway
188        painter
189            .ramp(
190                Aabb {
191                    min: Vec2::new(center.x - outside_radius, center.y - 16).with_z(base + 10),
192                    max: Vec2::new(center.x + (outside_radius / 2) + 8, center.y + 16)
193                        .with_z(base + 28),
194                },
195                Dir2::X,
196            )
197            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
198            .fill(grass.clone());
199        painter
200            .ramp(
201                Aabb {
202                    min: Vec2::new(center.x - outside_radius, center.y - 16).with_z(base + 9),
203                    max: Vec2::new(center.x + (outside_radius / 2) + 8, center.y + 16)
204                        .with_z(base + 27),
205                },
206                Dir2::X,
207            )
208            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
209            .fill(rock_broken.clone());
210
211        let clear_dist_x = outside_radius / 4;
212        let clear_dist_y = (2 * outside_radius) + (outside_radius / 10);
213        let clear_radius = 2 * outside_radius;
214        let clear_limiter = painter
215            .aabb(Aabb {
216                min: Vec2::new(center.x - outside_radius, center.y - 16).with_z(base + 9),
217                max: Vec2::new(center.x + (outside_radius / 2) + 8, center.y + 16)
218                    .with_z(base + 28),
219            })
220            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base));
221        for c in 0..=1 {
222            let clear_pos = Vec2::new(
223                center.x - clear_dist_x,
224                center.y - clear_dist_y + (c * (clear_dist_y * 2)),
225            );
226            painter
227                .cylinder(Aabb {
228                    min: (clear_pos - clear_radius).with_z(base + 9),
229                    max: (clear_pos + clear_radius).with_z(base + 28),
230                })
231                .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
232                .intersect(clear_limiter)
233                .clear();
234        }
235
236        // decor cones
237        for position in cone_positions {
238            for dir in LOCALITY {
239                let cone_pos = position + (dir * 2);
240                let cone_var = 10 + RandomField::new(0).get(cone_pos.with_z(base)) as i32 % 10;
241                painter
242                    .cone_with_radius(
243                        cone_pos.with_z(base),
244                        (cone_length / 3) as f32,
245                        (cone_length + cone_var) as f32,
246                    )
247                    .fill(rock_broken.clone());
248            }
249        }
250        // repaint platform grass layer
251        painter
252            .cylinder(Aabb {
253                min: (tree_pos - platform_size + 5).with_z(base - 10 + platform_size - 1),
254                max: (tree_pos + platform_size - 5).with_z(base - 10 + platform_size),
255            })
256            .fill(grass.clone());
257        // clear platform upwards
258        painter
259            .cylinder(Aabb {
260                min: (tree_pos - platform_size).with_z(base - 10 + platform_size),
261                max: (tree_pos + platform_size).with_z(base + platform_size),
262            })
263            .clear();
264        // main sphere
265        let sphere_limiter = painter.aabb(Aabb {
266            min: (center - diameter - 1).with_z(base - 2),
267            max: (center + diameter + 1).with_z(base + height_handle + 1),
268        });
269        painter
270            .sphere(Aabb {
271                min: (center - diameter - 1).with_z(base - (2 * diameter) + height_handle - 1),
272                max: (center + diameter + 1).with_z(base + height_handle + 1),
273            })
274            .intersect(sphere_limiter)
275            .fill(grass.clone());
276        painter
277            .sphere(Aabb {
278                min: (center - diameter + 1).with_z(base - (2 * diameter) + height_handle + 1),
279                max: (center + diameter - 1).with_z(base + height_handle - 1),
280            })
281            .intersect(sphere_limiter)
282            .fill(rock.clone());
283
284        // decor grass ring
285        let ring_radius = cone_radius as i32 + 4;
286        painter
287            .cylinder(Aabb {
288                min: (center - ring_radius).with_z(base),
289                max: (center + ring_radius).with_z(base + 4),
290            })
291            .fill(grass.clone());
292        let beams = cones + 8;
293        let beam_start_radius = cone_radius + 6_f32;
294        let beam_end_radius = cone_radius + 20_f32;
295        let beam_start = place_circular_as_vec(center, beam_start_radius, beams);
296        let beam_end = place_circular_as_vec(center, beam_end_radius, beams);
297        let room_size = self.room_size;
298
299        for b in 0..beams {
300            painter
301                .line(
302                    beam_start[b as usize].with_z(base + 2),
303                    beam_end[b as usize].with_z(base + 1),
304                    2.5,
305                )
306                .fill(grass.clone());
307        }
308        // entrance terrain clear
309        let entrance_clear = Vec2::new(entrance.x, entrance.y);
310        for c in 0..8 {
311            painter
312                .aabb(Aabb {
313                    min: (entrance_clear - 4 - c).with_z(base + c),
314                    max: (entrance_clear + 4 + c).with_z(base + 1 + c),
315                })
316                .clear();
317        }
318        // entrance tunnel
319        painter
320            .vault(
321                Aabb {
322                    min: Vec2::new(center.x + (diameter / 4), center.y - 12).with_z(base - 5),
323                    max: Vec2::new(center.x + (diameter / 2) + 9, center.y + 12).with_z(base + 22),
324                },
325                Dir2::X,
326            )
327            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
328            .fill(rock_broken.clone());
329        for v in 1..=5 {
330            painter
331                .vault(
332                    Aabb {
333                        min: Vec2::new(center.x + (diameter / 2) + 8 + v, center.y - 10 + v)
334                            .with_z(base - 5),
335                        max: Vec2::new(center.x + (diameter / 2) + 9 + v, center.y + 10 - v)
336                            .with_z(base + 22 - v),
337                    },
338                    Dir2::X,
339                )
340                .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
341                .fill(rock_broken.clone());
342        }
343        painter
344            .vault(
345                Aabb {
346                    min: Vec2::new(center.x + (diameter / 4), center.y - 4).with_z(base),
347                    max: Vec2::new(center.x + (diameter / 2) + 25, center.y + 4).with_z(base + 16),
348                },
349                Dir2::X,
350            )
351            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
352            .clear();
353        // entrance lanterns
354        painter
355            .aabb(Aabb {
356                min: Vec2::new(center.x + (diameter / 4), center.y - 4).with_z(base),
357                max: Vec2::new(center.x + (diameter / 2) + 15, center.y + 4).with_z(base + 1),
358            })
359            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
360            .fill(lanterns.clone());
361        // floor 0
362        painter
363            .cylinder(Aabb {
364                min: (center - beam_end_radius as i32 - 3).with_z(base - (diameter / 4) - 3),
365                max: (center + beam_end_radius as i32 + 3).with_z(base),
366            })
367            .fill(rock.clone());
368        // entrance trap
369        painter
370            .cylinder(Aabb {
371                min: Vec2::new(center.x + (diameter / 3) - 3, center.y - 2).with_z(base - 1),
372                max: Vec2::new(center.x + (diameter / 3) + 3, center.y + 3).with_z(base),
373            })
374            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
375            .fill(trap.clone());
376
377        // room hulls
378        for rooms in &self.room_positions {
379            let room_center = Vec2::new(rooms.x, rooms.y);
380            let floor = rooms.z + (room_size / 4) + 1;
381            painter
382                .superquadric(
383                    Aabb {
384                        min: (room_center - room_size - 2).with_z(floor - (room_size / 4) - 2),
385                        max: (room_center + room_size + 2).with_z(floor + (room_size / 4) + 2),
386                    },
387                    4.0,
388                )
389                .fill(rock.clone());
390            painter
391                .superquadric(
392                    Aabb {
393                        min: (room_center - room_size - 1).with_z(floor - (room_size / 4) - 1),
394                        max: (room_center + room_size + 1).with_z(floor + (room_size / 4) + 1),
395                    },
396                    4.0,
397                )
398                .fill(rock_broken.clone());
399        }
400        for rooms in &self.room_positions {
401            // clear rooms
402            let room_center = Vec2::new(rooms.x, rooms.y);
403            let floor = rooms.z + (room_size / 4) + 1;
404            painter
405                .superquadric(
406                    Aabb {
407                        min: (room_center - room_size).with_z(floor - (room_size / 4)),
408                        max: (room_center + room_size).with_z(floor + (room_size / 4)),
409                    },
410                    4.0,
411                )
412                .clear();
413            // room floor
414            painter
415                .aabb(Aabb {
416                    min: (room_center - room_size).with_z(floor - (room_size / 4) - 2),
417                    max: (room_center + room_size).with_z(floor - (room_size / 4) + 5),
418                })
419                .fill(rock.clone());
420            // room lanterns
421            painter
422                .aabb(Aabb {
423                    min: (room_center - room_size + 7).with_z(floor - (room_size / 4) + 5),
424                    max: (room_center + room_size - 7).with_z(floor - (room_size / 4) + 6),
425                })
426                .fill(lanterns.clone());
427        }
428        // mob rooms
429        for rooms in &self.mob_room_positions {
430            let room_center = Vec2::new(rooms.x, rooms.y);
431            let floor = rooms.z + (room_size / 4) + 1;
432            // inner room
433            painter
434                .aabb(Aabb {
435                    min: (room_center - (2 * (room_size / 3)) - 1)
436                        .with_z(floor - (room_size / 4) + 5),
437                    max: (room_center + (2 * (room_size / 3)) + 1).with_z(floor + (room_size / 4)),
438                })
439                .fill(rock_broken.clone());
440            painter
441                .aabb(Aabb {
442                    min: (room_center - (2 * (room_size / 3)) + 1)
443                        .with_z(floor - (room_size / 4) + 5),
444                    max: (room_center + (2 * (room_size / 3)) - 1)
445                        .with_z(floor + (room_size / 4) - 1),
446                })
447                .clear();
448            // inner room entries
449            painter
450                .vault(
451                    Aabb {
452                        min: Vec2::new(
453                            room_center.x - (2 * (room_size / 3)) - 1,
454                            room_center.y - 4,
455                        )
456                        .with_z(floor - (room_size / 4) + 5),
457                        max: Vec2::new(
458                            room_center.x + (2 * (room_size / 3)) + 1,
459                            room_center.y + 4,
460                        )
461                        .with_z(floor + (room_size / 8)),
462                    },
463                    Dir2::X,
464                )
465                .clear();
466            painter
467                .vault(
468                    Aabb {
469                        min: Vec2::new(
470                            room_center.x - 4,
471                            room_center.y - (2 * (room_size / 3)) - 1,
472                        )
473                        .with_z(floor - (room_size / 4) + 5),
474                        max: Vec2::new(
475                            room_center.x + 4,
476                            room_center.y + (2 * (room_size / 3)) + 1,
477                        )
478                        .with_z(floor + (room_size / 8)),
479                    },
480                    Dir2::Y,
481                )
482                .clear();
483            // room lanterns inner
484            painter
485                .aabb(Aabb {
486                    min: (room_center - (2 * (room_size / 3)) + 1)
487                        .with_z(floor - (room_size / 4) + 5),
488                    max: (room_center + (2 * (room_size / 3)) - 1)
489                        .with_z(floor - (room_size / 4) + 6),
490                })
491                .fill(lanterns.clone());
492            for dir in DIAGONALS {
493                let pillar_pos = room_center + dir * ((2 * (room_size / 3)) - 6);
494                let stair_pos = Vec2::new(
495                    room_center.x + dir.x * ((2 * (room_size / 3)) - 10),
496                    room_center.y + dir.y * ((2 * (room_size / 3)) - 3),
497                );
498                if (RandomField::new(0).get((pillar_pos).with_z(base)) % 3) > 0 {
499                    // stairs
500                    painter
501                        .line(
502                            Vec2::new(room_center.x, stair_pos.y)
503                                .with_z(floor - (room_size / 4) - 1),
504                            stair_pos.with_z(floor - (room_size / 4) + 6),
505                            4.0,
506                        )
507                        .fill(rock.clone());
508                    painter
509                        .cylinder(Aabb {
510                            min: (pillar_pos - 6).with_z(floor - (room_size / 4) + 5),
511                            max: (pillar_pos + 6).with_z(floor - (room_size / 4) + 6),
512                        })
513                        .fill(rock.clone());
514                    painter
515                        .cylinder(Aabb {
516                            min: (pillar_pos - 6).with_z(floor - (room_size / 4) + 6),
517                            max: (pillar_pos + 6).with_z(floor - (room_size / 4) + 7),
518                        })
519                        .fill(iron_spikes.clone());
520                    painter
521                        .cylinder(Aabb {
522                            min: (pillar_pos - 5).with_z(floor - (room_size / 4) + 5),
523                            max: (pillar_pos + 5).with_z(floor - (room_size / 4) + 9),
524                        })
525                        .fill(rock_broken.clone());
526                    painter
527                        .cylinder(Aabb {
528                            min: (pillar_pos - 6).with_z(floor - (room_size / 4) + 9),
529                            max: (pillar_pos + 7).with_z(floor - (room_size / 4) + 10),
530                        })
531                        .fill(rock.clone());
532                    // chests
533                    if (RandomField::new(0).get((pillar_pos).with_z(base)) % 5) == 0 {
534                        painter
535                            .aabb(Aabb {
536                                min: pillar_pos.with_z(floor - (room_size / 4) + 9),
537                                max: (pillar_pos + 1).with_z(floor - (room_size / 4) + 10),
538                            })
539                            .fill(rock.clone());
540                        painter
541                            .aabb(Aabb {
542                                min: pillar_pos.with_z(floor - (room_size / 4) + 10),
543                                max: (pillar_pos + 1).with_z(floor - (room_size / 4) + 11),
544                            })
545                            .fill(Fill::Block(Block::air(SpriteKind::DungeonChest3)));
546                    }
547                    // room npcs
548                    for _ in 0..=rng.random_range(0..2) {
549                        // archers on pillars
550                        painter.spawn(
551                            EntityInfo::at(pillar_pos.with_z(floor - (room_size / 4) + 11).as_())
552                                .with_asset_expect(
553                                    "common.entity.dungeon.haniwa.archer",
554                                    &mut rng,
555                                    None,
556                                ),
557                        );
558                    }
559                }
560            }
561            for n in 0..=rng.random_range(2..=4) {
562                let select =
563                    (RandomField::new(0).get((room_center + n).with_z(floor)) % 2) as usize;
564                painter.spawn(
565                    EntityInfo::at(
566                        (room_center + 10 + n)
567                            .with_z(floor - (room_size / 4) + 6)
568                            .as_(),
569                    )
570                    .with_asset_expect(npcs[select], &mut rng, None),
571                )
572            }
573            let effigy_pos = (room_center - 8).with_z(floor - (room_size / 4) + 6);
574            if (RandomField::new(0).get(effigy_pos) % 2) as usize > 0 {
575                painter.spawn(EntityInfo::at(effigy_pos.as_()).with_asset_expect(
576                    "common.entity.dungeon.haniwa.ancienteffigy",
577                    &mut rng,
578                    None,
579                ));
580            }
581            // room chest
582            match RandomField::new(0).get(room_center.with_z(base)) as i32 % 3 {
583                0 => {
584                    for dir in CARDINALS {
585                        let sentry_pos = room_center + dir * 10;
586                        painter.spawn(
587                            EntityInfo::at(sentry_pos.with_z(floor - (room_size / 4) + 6).as_())
588                                .with_asset_expect(
589                                    "common.entity.dungeon.haniwa.sentry",
590                                    &mut rng,
591                                    None,
592                                ),
593                        )
594                    }
595                    painter
596                        .aabb(Aabb {
597                            min: (room_center - 1).with_z(floor - (room_size / 4) + 5),
598                            max: (room_center + 2).with_z(floor - (room_size / 4) + 6),
599                        })
600                        .fill(rock.clone());
601                    painter
602                        .aabb(Aabb {
603                            min: room_center.with_z(floor - (room_size / 4) + 6),
604                            max: (room_center + 1).with_z(floor - (room_size / 4) + 7),
605                        })
606                        .fill(Fill::Block(Block::air(SpriteKind::HaniwaUrn)));
607                },
608                1 => {
609                    painter
610                        .cylinder(Aabb {
611                            min: (room_center - 8).with_z(floor - (room_size / 4) + 5),
612                            max: (room_center + 8).with_z(floor - (room_size / 4) + 6),
613                        })
614                        .fill(rock_iron_spikes.clone());
615                    painter
616                        .cylinder(Aabb {
617                            min: (room_center - 7).with_z(floor - (room_size / 4) + 5),
618                            max: (room_center + 7).with_z(floor - (room_size / 4) + 6),
619                        })
620                        .fill(rock.clone());
621                    painter
622                        .cylinder(Aabb {
623                            min: (room_center - 7).with_z(floor - (room_size / 4) + 6),
624                            max: (room_center + 7).with_z(floor - (room_size / 4) + 7),
625                        })
626                        .fill(rock_iron_spikes.clone());
627                    painter
628                        .cylinder(Aabb {
629                            min: (room_center - 6).with_z(floor - (room_size / 4) + 6),
630                            max: (room_center + 6).with_z(floor - (room_size / 4) + 7),
631                        })
632                        .fill(rock.clone());
633                    painter
634                        .cylinder(Aabb {
635                            min: (room_center - 6).with_z(floor - (room_size / 4) + 7),
636                            max: (room_center + 6).with_z(floor - (room_size / 4) + 8),
637                        })
638                        .fill(rock_iron_spikes.clone());
639                    painter
640                        .aabb(Aabb {
641                            min: (room_center - 1).with_z(floor - (room_size / 4) + 7),
642                            max: (room_center + 2).with_z(floor - (room_size / 4) + 8),
643                        })
644                        .fill(rock.clone());
645                    painter
646                        .aabb(Aabb {
647                            min: room_center.with_z(floor - (room_size / 4) + 8),
648                            max: (room_center + 1).with_z(floor - (room_size / 4) + 9),
649                        })
650                        .fill(Fill::Block(Block::air(SpriteKind::HaniwaUrn)));
651                },
652                _ => {
653                    for c in 0..=7 {
654                        painter
655                            .cylinder(Aabb {
656                                min: (room_center - 10 + c).with_z(floor - (room_size / 4) + 4),
657                                max: (room_center + 10 - c).with_z(floor - (room_size / 4) + 5),
658                            })
659                            .fill(match c {
660                                0 | 2 | 4 | 6 => trap.clone(),
661                                _ => rock.clone(),
662                            });
663                    }
664
665                    painter
666                        .aabb(Aabb {
667                            min: (room_center - 1).with_z(floor - (room_size / 4) + 5),
668                            max: (room_center + 2).with_z(floor - (room_size / 4) + 6),
669                        })
670                        .fill(rock.clone());
671                    painter
672                        .aabb(Aabb {
673                            min: room_center.with_z(floor - (room_size / 4) + 6),
674                            max: (room_center + 1).with_z(floor - (room_size / 4) + 7),
675                        })
676                        .fill(Fill::Block(Block::air(SpriteKind::HaniwaUrn)));
677                },
678            }
679        }
680        // center rooms
681        for rooms in &self.center_room_positions {
682            let floor = rooms.z + (room_size / 4) + 1;
683            // room decor
684            for dir in NEIGHBORS {
685                let position = center + dir * 20;
686                for p in 0..4 {
687                    painter
688                        .aabb(Aabb {
689                            min: (position - 1 - p).with_z(floor - (room_size / 4) + 5 + (4 * p)),
690                            max: (position + 2 + p).with_z(floor - (room_size / 4) + 9 + (4 * p)),
691                        })
692                        .fill(rock_broken.clone());
693                }
694                for t in 0..2 {
695                    let trap_pos = center + (dir * (12 + (t * 14)));
696                    if RandomField::new(0).get((trap_pos).with_z(floor)) % 3 < 1 {
697                        painter
698                            .aabb(Aabb {
699                                min: (trap_pos - 1).with_z(floor - (room_size / 4) + 4),
700                                max: (trap_pos + 1).with_z(floor - (room_size / 4) + 5),
701                            })
702                            .fill(trap.clone());
703                        painter
704                            .aabb(Aabb {
705                                min: (trap_pos - 1).with_z(floor - (room_size / 4) + 5),
706                                max: (trap_pos + 1).with_z(floor - (room_size / 4) + 6),
707                            })
708                            .clear();
709                    }
710                }
711            }
712        }
713        // center room stairs
714        for f in 0..(self.center_room_positions.len() - 1) {
715            let floor = self.center_room_positions[f].z + (room_size / 4) + 1;
716            let stairs_floor = floor - 5;
717            let stairs_start =
718                Vec2::new(center.x - (diameter / 6) + 2, center.y - (diameter / 6) + 7);
719            for s in 0..(diameter / 5) {
720                painter
721                    .vault(
722                        Aabb {
723                            min: Vec2::new(stairs_start.x + s - 1, stairs_start.y - 4)
724                                .with_z(stairs_floor - s - 2),
725                            max: Vec2::new(stairs_start.x + s, stairs_start.y + 4)
726                                .with_z(stairs_floor + 12 - s + 2),
727                        },
728                        Dir2::X,
729                    )
730                    .fill(rock_broken.clone());
731            }
732            for s in 0..(diameter / 5) {
733                painter
734                    .vault(
735                        Aabb {
736                            min: Vec2::new(stairs_start.x + s - 1, stairs_start.y - 2)
737                                .with_z(stairs_floor - s),
738                            max: Vec2::new(stairs_start.x + s, stairs_start.y + 2)
739                                .with_z(stairs_floor + 10 - s),
740                        },
741                        Dir2::X,
742                    )
743                    .clear();
744                painter
745                    .aabb(Aabb {
746                        min: Vec2::new(stairs_start.x + s - 1, stairs_start.y - 2)
747                            .with_z(stairs_floor - 1 - s),
748                        max: Vec2::new(stairs_start.x + s, stairs_start.y + 2)
749                            .with_z(stairs_floor - s),
750                    })
751                    .fill(rock.clone());
752                painter
753                    .aabb(Aabb {
754                        min: Vec2::new(stairs_start.x + s - 1, stairs_start.y - 2)
755                            .with_z(stairs_floor - s),
756                        max: Vec2::new(stairs_start.x + s, stairs_start.y + 2)
757                            .with_z(stairs_floor + 1 - s),
758                    })
759                    .fill(lanterns.clone());
760
761                let doors = [0, 8, 16, 24];
762                if doors.contains(&s) {
763                    painter
764                        .vault(
765                            Aabb {
766                                min: Vec2::new(stairs_start.x + s - 1, stairs_start.y - 2)
767                                    .with_z(stairs_floor - s),
768                                max: Vec2::new(stairs_start.x + s, stairs_start.y + 2)
769                                    .with_z(stairs_floor + 10 - s),
770                            },
771                            Dir2::X,
772                        )
773                        .fill(key_door.clone());
774                    painter
775                        .aabb(Aabb {
776                            min: Vec2::new(stairs_start.x + s - 1, stairs_start.y)
777                                .with_z(stairs_floor + 2 - s),
778                            max: Vec2::new(stairs_start.x + s, stairs_start.y + 1)
779                                .with_z(stairs_floor + 3 - s),
780                        })
781                        .fill(key_hole.clone());
782                }
783            }
784        }
785
786        // stair case
787        painter
788            .aabb(Aabb {
789                min: Vec2::new(center.x + 3, center.y - 6).with_z(base - (diameter / 4) - 5),
790                max: Vec2::new(center.x + 14, center.y + 6).with_z(base - (diameter / 4) + 15),
791            })
792            .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
793            .fill(rock_broken.clone());
794        // tunnel stairs
795        for s in 0..((diameter / 4) - 3) {
796            painter
797                .vault(
798                    Aabb {
799                        min: Vec2::new(center.x + (diameter / 4) - s - 1, center.y - 5)
800                            .with_z(base - s - 1),
801                        max: Vec2::new(center.x + (diameter / 4) - s, center.y + 5)
802                            .with_z(base + 16 - s + 1),
803                    },
804                    Dir2::X,
805                )
806                .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
807                .fill(rock_broken.clone());
808
809            painter
810                .vault(
811                    Aabb {
812                        min: Vec2::new(center.x + (diameter / 4) - s - 1, center.y - 4)
813                            .with_z(base - s),
814                        max: Vec2::new(center.x + (diameter / 4) - s, center.y + 4)
815                            .with_z(base + 16 - s),
816                    },
817                    Dir2::X,
818                )
819                .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
820                .clear();
821            painter
822                .aabb(Aabb {
823                    min: Vec2::new(center.x + (diameter / 4) - s - 1, center.y - 4)
824                        .with_z(base - 1 - s),
825                    max: Vec2::new(center.x + (diameter / 4) - s, center.y + 4).with_z(base - s),
826                })
827                .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
828                .fill(rock.clone());
829            painter
830                .aabb(Aabb {
831                    min: Vec2::new(center.x + (diameter / 4) - s - 1, center.y - 4)
832                        .with_z(base - s),
833                    max: Vec2::new(center.x + (diameter / 4) - s, center.y + 4)
834                        .with_z(base + 1 - s),
835                })
836                .rotate_about(Mat3::rotation_z(self.rotation).as_(), center.with_z(base))
837                .fill(lanterns.clone());
838        }
839
840        // tree model
841        lazy_static! {
842            pub static ref MODEL: AssetHandle<StructuresGroup> =
843                PrefabStructure::load_group("site_structures.haniwa.bonsai");
844        }
845        let model_pos = tree_pos.with_z(base - 10 + platform_size);
846        let rng_val = RandomField::new(0).get(model_pos) % 10;
847        let model = MODEL.read();
848        let model = model[rng_val as usize % model.len()].clone();
849        painter
850            .prim(Primitive::Prefab(Box::new(model.clone())))
851            .translate(model_pos)
852            .fill(Fill::Prefab(Box::new(model), model_pos, rng_val));
853
854        // mini_bosses
855        let golem_pos = Vec3::new(
856            self.mini_boss_room_positions[0].x,
857            self.mini_boss_room_positions[0].y,
858            self.mini_boss_room_positions[0].z + 5,
859        );
860        painter.spawn(EntityInfo::at(golem_pos.as_()).with_asset_expect(
861            "common.entity.dungeon.haniwa.claygolem",
862            &mut rng,
863            None,
864        ));
865        // mid_boss
866        let mid_boss = [
867            "common.entity.dungeon.haniwa.general",
868            "common.entity.dungeon.haniwa.claysteed",
869        ];
870        for npc in mid_boss.iter() {
871            painter.spawn(
872                EntityInfo::at(
873                    Vec3::new(
874                        self.mini_boss_room_positions[1].x,
875                        self.mini_boss_room_positions[1].y,
876                        self.mini_boss_room_positions[1].z + 5,
877                    )
878                    .as_(),
879                )
880                .with_asset_expect(npc, &mut rng, None),
881            );
882        }
883        // boss
884        painter.spawn(
885            EntityInfo::at(
886                Vec3::new(
887                    self.boss_room_position.x,
888                    self.boss_room_position.y,
889                    self.boss_room_position.z + 5,
890                )
891                .as_(),
892            )
893            .with_asset_expect(
894                "common.entity.dungeon.haniwa.gravewarden",
895                &mut rng,
896                None,
897            ),
898        );
899        let bonerattler_pos = (center + ((entrance - center) / 3)).with_z(base);
900        for _ in 0..(1 + RandomField::new(0).get(center.with_z(base)) % 2) as i32 {
901            painter.spawn(EntityInfo::at(bonerattler_pos.as_()).with_asset_expect(
902                "common.entity.dungeon.haniwa.claysteed",
903                &mut rng,
904                None,
905            ))
906        }
907    }
908}