veloren_world/site/plot/
myrmidon_arena.rs

1use super::*;
2use crate::{
3    Land,
4    site::generation::place_circular,
5    util::{NEIGHBORS, RandomField, Sampler, within_distance},
6};
7use common::generation::EntityInfo;
8use rand::prelude::*;
9use std::sync::Arc;
10use vek::*;
11
12pub struct ArenaData {
13    base: i32,
14    center: Vec2<i32>,
15    entry_pos: Vec2<i32>,
16    boss_pos: Vec2<i32>,
17    radius: i32,
18}
19
20/// Represents house data generated by the `generate()` method
21pub struct MyrmidonArena {
22    /// Axis aligned bounding region for the house
23    bounds: Aabr<i32>,
24    /// Approximate altitude of the door tile
25    pub(crate) alt: i32,
26    pub(crate) arena_data: ArenaData,
27}
28
29impl MyrmidonArena {
30    pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
31        let bounds = Aabr {
32            min: site.tile_wpos(tile_aabr.min),
33            max: site.tile_wpos(tile_aabr.max),
34        };
35        let center = bounds.center();
36        let base = land.get_alt_approx(center) as i32 + 2;
37        let diameter = (bounds.max.x - bounds.min.x).min(bounds.max.y - bounds.min.y) - 20;
38        let radius = diameter / 2;
39        let entry_pos = Vec2::new(center.x, center.y + radius - 12);
40        let boss_pos = Vec2::new(center.x, center.y - radius + 12);
41
42        let arena_data = ArenaData {
43            base,
44            center,
45            entry_pos,
46            boss_pos,
47            radius,
48        };
49
50        Self {
51            bounds,
52            alt: base,
53            arena_data,
54        }
55    }
56
57    pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
58        SpawnRules {
59            waypoints: false,
60            trees: !within_distance(wpos, self.bounds.center(), 85),
61            ..SpawnRules::default()
62        }
63    }
64}
65
66impl Structure for MyrmidonArena {
67    #[cfg(feature = "use-dyn-lib")]
68    const UPDATE_FN: &'static [u8] = b"render_myrmidon_arena\0";
69
70    #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_myrmidon_arena"))]
71    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
72        let base = self.arena_data.base + 1;
73        let center = self.arena_data.center;
74        let mut rng = rand::rng();
75        let sandstone_unbroken = Fill::Sampling(Arc::new(|center| {
76            Some(match (RandomField::new(0).get(center)) % 37 {
77                0..=8 => Block::new(BlockKind::Rock, Rgb::new(245, 212, 129)),
78                9..=17 => Block::new(BlockKind::Rock, Rgb::new(246, 214, 133)),
79                18..=26 => Block::new(BlockKind::Rock, Rgb::new(247, 216, 136)),
80                27..=35 => Block::new(BlockKind::Rock, Rgb::new(248, 219, 142)),
81                _ => Block::new(BlockKind::Rock, Rgb::new(235, 178, 99)),
82            })
83        }));
84        let sandstone = Fill::Sampling(Arc::new(|center| {
85            Some(match (RandomField::new(0).get(center)) % 42 {
86                0..=8 => Block::new(BlockKind::Rock, Rgb::new(245, 212, 129)),
87                9..=17 => Block::new(BlockKind::Rock, Rgb::new(246, 214, 133)),
88                18..=26 => Block::new(BlockKind::Rock, Rgb::new(247, 216, 136)),
89                27..=35 => Block::new(BlockKind::Rock, Rgb::new(248, 219, 142)),
90                36..=37 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
91                _ => Block::new(BlockKind::Rock, Rgb::new(235, 178, 99)),
92            })
93        }));
94        let weak_sandstone = Fill::Sampling(Arc::new(|center| {
95            Some(match (RandomField::new(0).get(center)) % 42 {
96                0..=8 => Block::new(BlockKind::WeakRock, Rgb::new(245, 212, 129)),
97                9..=17 => Block::new(BlockKind::WeakRock, Rgb::new(246, 214, 133)),
98                18..=26 => Block::new(BlockKind::WeakRock, Rgb::new(247, 216, 136)),
99                27..=35 => Block::new(BlockKind::WeakRock, Rgb::new(248, 219, 142)),
100                36..=37 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
101                _ => Block::new(BlockKind::WeakRock, Rgb::new(235, 178, 99)),
102            })
103        }));
104        let roof_color = Fill::Brick(BlockKind::Sand, Rgb::new(115, 32, 2), 12);
105        let radius = self.arena_data.radius;
106        // foundation
107        painter
108            .cylinder(Aabb {
109                min: (center - radius).with_z(base - 80),
110                max: (center + radius).with_z(base),
111            })
112            .fill(sandstone_unbroken.clone());
113        // center carves
114        painter
115            .cylinder(Aabb {
116                min: (center - radius + 30).with_z(base - 20),
117                max: (center + radius - 30).with_z(base - 12),
118            })
119            .clear();
120        painter
121            .cylinder(Aabb {
122                min: (center - radius + 33).with_z(base - 12),
123                max: (center + radius - 33).with_z(base - 11),
124            })
125            .clear();
126        painter
127            .cylinder(Aabb {
128                min: (center - radius + 20).with_z(base - 11),
129                max: (center + radius - 20).with_z(base),
130            })
131            .clear();
132        painter
133            .cylinder(Aabb {
134                min: (center - radius + 15).with_z(base - 11),
135                max: (center + radius - 15).with_z(base - 1),
136            })
137            .clear();
138        // clear terrain
139        painter
140            .cylinder(Aabb {
141                min: (center - radius + 5).with_z(base),
142                max: (center + radius - 5).with_z(base + 30),
143            })
144            .clear();
145
146        // circle
147        let circle_radius = radius / 5;
148        painter
149            .cylinder(Aabb {
150                min: (center - circle_radius).with_z(base - 20),
151                max: (center + circle_radius).with_z(base - 19),
152            })
153            .fill(sandstone_unbroken.clone());
154        painter
155            .cylinder(Aabb {
156                min: (center - circle_radius + 1).with_z(base - 19),
157                max: (center + circle_radius - 1).with_z(base - 18),
158            })
159            .fill(sandstone.clone());
160        painter
161            .cylinder(Aabb {
162                min: (center - circle_radius + 2).with_z(base - 19),
163                max: (center + circle_radius - 2).with_z(base - 18),
164            })
165            .clear();
166
167        for dir in CARDINALS {
168            let clear_pos = center + dir * circle_radius;
169            let clear_rand = RandomField::new(0).get(clear_pos.with_z(base)) % 2;
170            if clear_rand < 1 {
171                painter
172                    .cylinder(Aabb {
173                        min: (clear_pos - 6).with_z(base - 19),
174                        max: (clear_pos + 6).with_z(base - 18),
175                    })
176                    .clear();
177            }
178        }
179        let pillars = 3 + (RandomField::new(0).get(center.with_z(base + 2)) % 3) as i32;
180        let center_pillar_positions = place_circular(center, (circle_radius - 5) as f32, pillars);
181        for pillar in center_pillar_positions {
182            let pillar_rand = RandomField::new(0).get(pillar.with_z(base)) % 3;
183            if pillar_rand > 0 {
184                let pillar_heigth = pillar_rand as i32;
185                painter
186                    .cylinder(Aabb {
187                        min: (pillar - 3).with_z(base - 19),
188                        max: (pillar + 3).with_z(base - 18),
189                    })
190                    .fill(sandstone.clone());
191                painter
192                    .cylinder(Aabb {
193                        min: (pillar - 2).with_z(base - 18),
194                        max: (pillar + 2).with_z(base - 18 + pillar_heigth),
195                    })
196                    .fill(sandstone.clone());
197                for dir in CARDINALS {
198                    let clear_pos = pillar + dir * 3;
199                    let clear_var = RandomField::new(0).get(clear_pos.with_z(base)) % 2;
200
201                    if clear_var > 0 {
202                        painter
203                            .sphere_with_radius(clear_pos.with_z(base - 18 + pillar_heigth), 3.0)
204                            .clear();
205                    }
206                }
207            }
208        }
209        // pillars & platforms
210        let pillar_positions = place_circular(center, (radius - 5) as f32, 60);
211        let outer_pillar_positions = place_circular(center, (radius + 5) as f32, 60);
212        let top_decor_positions = place_circular(center, radius as f32, 100);
213
214        let top_platform_height = 45;
215        let platform_1_height = top_platform_height / 3;
216        let platform_2_height = platform_1_height * 2;
217        // platform 1
218        painter
219            .cylinder(Aabb {
220                min: (center - radius - 7).with_z(base + platform_1_height),
221                max: (center + radius + 7).with_z(base + platform_1_height + 1),
222            })
223            .fill(sandstone.clone());
224        painter
225            .cylinder(Aabb {
226                min: (center - radius + 5).with_z(base + platform_1_height),
227                max: (center + radius - 5).with_z(base + platform_1_height + 1),
228            })
229            .clear();
230        // platform 2
231        painter
232            .cylinder(Aabb {
233                min: (center - radius - 7).with_z(base + platform_2_height),
234                max: (center + radius + 7).with_z(base + platform_2_height + 1),
235            })
236            .fill(sandstone.clone());
237        painter
238            .cylinder(Aabb {
239                min: (center - radius + 5).with_z(base + platform_2_height),
240                max: (center + radius - 5).with_z(base + platform_2_height + 1),
241            })
242            .clear();
243        for pillar_pos in pillar_positions {
244            painter
245                .cylinder(Aabb {
246                    min: (pillar_pos - 5).with_z(base - 1),
247                    max: (pillar_pos + 5).with_z(base + (2 * (top_platform_height / 3))),
248                })
249                .fill(sandstone.clone());
250            painter
251                .cylinder(Aabb {
252                    min: (pillar_pos - 3).with_z(base + (2 * (top_platform_height / 3))),
253                    max: (pillar_pos + 3).with_z(base + (2 * (top_platform_height / 3)) + 1),
254                })
255                .fill(sandstone.clone());
256            painter
257                .cylinder(Aabb {
258                    min: (pillar_pos - 2).with_z(base + (2 * (top_platform_height / 3)) + 1),
259                    max: (pillar_pos + 2).with_z(base + top_platform_height),
260                })
261                .fill(sandstone.clone());
262            painter
263                .cylinder(Aabb {
264                    min: (pillar_pos - 5).with_z(base + top_platform_height),
265                    max: (pillar_pos + 5).with_z(base + top_platform_height + 1),
266                })
267                .fill(sandstone.clone());
268        }
269        for outer_pillar_pos in outer_pillar_positions {
270            painter
271                .cylinder(Aabb {
272                    min: (outer_pillar_pos - 3).with_z(base - 10),
273                    max: (outer_pillar_pos + 3).with_z(base - 5),
274                })
275                .fill(sandstone_unbroken.clone());
276            painter
277                .cylinder(Aabb {
278                    min: (outer_pillar_pos - 3).with_z(base - 5),
279                    max: (outer_pillar_pos + 3).with_z(base - 1),
280                })
281                .fill(sandstone.clone());
282            painter
283                .cylinder(Aabb {
284                    min: (outer_pillar_pos - 3).with_z(base + platform_1_height + 1),
285                    max: (outer_pillar_pos + 3).with_z(base + platform_1_height + 2),
286                })
287                .fill(sandstone.clone());
288            painter
289                .cylinder(Aabb {
290                    min: (outer_pillar_pos - 3).with_z(base + platform_2_height + 1),
291                    max: (outer_pillar_pos + 3).with_z(base + platform_2_height + 2),
292                })
293                .fill(sandstone.clone());
294
295            painter
296                .cylinder(Aabb {
297                    min: (outer_pillar_pos - 2).with_z(base - 1),
298                    max: (outer_pillar_pos + 2).with_z(base + top_platform_height),
299                })
300                .fill(sandstone.clone());
301
302            painter
303                .cylinder(Aabb {
304                    min: (outer_pillar_pos - 5).with_z(base + top_platform_height),
305                    max: (outer_pillar_pos + 5).with_z(base + top_platform_height + 1),
306                })
307                .fill(sandstone.clone());
308        }
309
310        for top_decor_pos in top_decor_positions {
311            for d in 0..4 {
312                painter
313                    .cylinder(Aabb {
314                        min: (top_decor_pos - 6 + d).with_z(base + top_platform_height + d),
315                        max: (top_decor_pos + 6 - d).with_z(base + top_platform_height + 1 + d),
316                    })
317                    .fill(sandstone.clone());
318            }
319
320            let decay = (RandomField::new(0).get(top_decor_pos.with_z(base)) % 6) as i32;
321
322            if decay < 1 {
323                let decay_rand =
324                    12.0 + (RandomField::new(0).get(top_decor_pos.with_z(base)) % 6) as f32;
325
326                painter
327                    .sphere_with_radius(
328                        top_decor_pos.with_z(base + top_platform_height + 10),
329                        decay_rand,
330                    )
331                    .clear();
332            }
333        }
334
335        // entries
336        let entry_pos = self.arena_data.entry_pos;
337        let entry_pillar_pos_1 = Vec2::new(center.x, center.y + radius - 16);
338        let pillar_x_offset = [-12, 12];
339        let pillar_y_offset = [0, 29];
340
341        for pillar_x_dir in pillar_x_offset {
342            for pillar_y_dir in pillar_y_offset {
343                let pillar_pos = Vec2::new(
344                    entry_pillar_pos_1.x + pillar_x_dir,
345                    entry_pillar_pos_1.y + pillar_y_dir,
346                );
347
348                painter
349                    .cylinder(Aabb {
350                        min: (pillar_pos - 3).with_z(base - 2),
351                        max: (pillar_pos + 3).with_z(base + 1),
352                    })
353                    .fill(sandstone.clone());
354
355                painter
356                    .cylinder(Aabb {
357                        min: (pillar_pos - 2).with_z(base + 1),
358                        max: (pillar_pos + 2).with_z(base + platform_2_height),
359                    })
360                    .fill(sandstone.clone());
361                painter
362                    .cylinder(Aabb {
363                        min: (pillar_pos - 3).with_z(base + platform_2_height),
364                        max: (pillar_pos + 3).with_z(base + platform_2_height + 1),
365                    })
366                    .fill(sandstone.clone());
367            }
368        }
369
370        painter
371            .vault(
372                Aabb {
373                    min: Vec2::new(entry_pos.x - 15, entry_pos.y).with_z(base - 2),
374                    max: Vec2::new(entry_pos.x + 15, entry_pos.y + 12)
375                        .with_z(base + platform_2_height),
376                },
377                Dir::Y,
378            )
379            .fill(sandstone.clone());
380        painter
381            .vault(
382                Aabb {
383                    min: Vec2::new(entry_pos.x - 10, entry_pos.y).with_z(base),
384                    max: Vec2::new(entry_pos.x + 10, entry_pos.y + 12)
385                        .with_z(base + platform_2_height - 5),
386                },
387                Dir::Y,
388            )
389            .clear();
390        let height_handle = 5;
391        painter
392            .aabb(Aabb {
393                min: Vec2::new(entry_pos.x - 15, entry_pos.y - 7)
394                    .with_z(base + platform_2_height - 1 + height_handle),
395                max: Vec2::new(entry_pos.x + 15, entry_pos.y + 28)
396                    .with_z(base + platform_2_height + height_handle),
397            })
398            .fill(sandstone.clone());
399
400        painter
401            .aabb(Aabb {
402                min: Vec2::new(entry_pos.x - 16, entry_pos.y - 8)
403                    .with_z(base + platform_2_height - 2 + height_handle),
404                max: Vec2::new(entry_pos.x + 16, entry_pos.y + 29)
405                    .with_z(base + platform_2_height - 1 + height_handle),
406            })
407            .fill(sandstone.clone());
408        painter
409            .aabb(Aabb {
410                min: Vec2::new(entry_pos.x - 15, entry_pos.y - 7)
411                    .with_z(base + platform_2_height - 3 + height_handle),
412                max: Vec2::new(entry_pos.x + 15, entry_pos.y + 28)
413                    .with_z(base + platform_2_height - 2 + height_handle),
414            })
415            .fill(sandstone.clone());
416        painter
417            .aabb(Aabb {
418                min: Vec2::new(entry_pos.x - 16, entry_pos.y - 8)
419                    .with_z(base + platform_2_height - 4 + height_handle),
420                max: Vec2::new(entry_pos.x + 16, entry_pos.y + 29)
421                    .with_z(base + platform_2_height - 3 + height_handle),
422            })
423            .fill(sandstone.clone());
424
425        painter
426            .gable(
427                Aabb {
428                    min: Vec2::new(entry_pos.x - 18, entry_pos.y - 8)
429                        .with_z(base + platform_2_height + height_handle),
430                    max: Vec2::new(entry_pos.x + 18, entry_pos.y + 29)
431                        .with_z(base + platform_2_height + 4 + height_handle),
432                },
433                16,
434                Dir::Y,
435            )
436            .fill(sandstone.clone());
437
438        painter
439            .gable(
440                Aabb {
441                    min: Vec2::new(entry_pos.x - 16, entry_pos.y - 6)
442                        .with_z(base + platform_2_height + height_handle),
443                    max: Vec2::new(entry_pos.x + 16, entry_pos.y + 27)
444                        .with_z(base + platform_2_height + 5 + height_handle),
445                },
446                16,
447                Dir::Y,
448            )
449            .fill(roof_color.clone());
450        for g in 0..8 {
451            painter
452                .gable(
453                    Aabb {
454                        min: Vec2::new(entry_pos.x - 17, entry_pos.y - 4 + (4 * g))
455                            .with_z(base + platform_2_height + height_handle),
456                        max: Vec2::new(entry_pos.x + 17, entry_pos.y - 3 + (4 * g))
457                            .with_z(base + platform_2_height + 6 + height_handle),
458                    },
459                    16,
460                    Dir::Y,
461                )
462                .fill(roof_color.clone());
463        }
464        // chest chamber & exit
465        painter
466            .vault(
467                Aabb {
468                    min: Vec2::new(entry_pos.x - 3, entry_pos.y - 19).with_z(base - 20),
469                    max: Vec2::new(entry_pos.x + 3, entry_pos.y + 5).with_z(base - 12),
470                },
471                Dir::Y,
472            )
473            .clear();
474        painter
475            .aabb(Aabb {
476                min: Vec2::new(entry_pos.x - 3, entry_pos.y - 1).with_z(base - 20),
477                max: Vec2::new(entry_pos.x + 3, entry_pos.y + 1).with_z(base),
478            })
479            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
480
481        painter
482            .aabb(Aabb {
483                min: Vec2::new(entry_pos.x - 1, entry_pos.y - 1).with_z(base - 18),
484                max: Vec2::new(entry_pos.x, entry_pos.y).with_z(base - 17),
485            })
486            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyhole)));
487
488        painter
489            .aabb(Aabb {
490                min: Vec2::new(entry_pos.x - 1, entry_pos.y + 4).with_z(base - 20),
491                max: Vec2::new(entry_pos.x + 1, entry_pos.y + 5).with_z(base - 19),
492            })
493            .fill(Fill::Block(Block::air(SpriteKind::DungeonChest4)));
494
495        // npcs
496        let mob_positions_low = place_circular(center, (radius - 12) as f32, 20);
497        for npc_position in mob_positions_low {
498            let entities = [
499                "common.entity.dungeon.myrmidon.hoplite",
500                "common.entity.dungeon.myrmidon.marksman",
501                "common.entity.dungeon.myrmidon.strategian",
502            ];
503            let npc_pos = npc_position.with_z(base + 2);
504            let npc = entities[(RandomField::new(0).get(npc_pos) % entities.len() as u32) as usize];
505            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(npc, &mut rng, None));
506        }
507        let mob_positions_pit = place_circular(center, (radius / 3) as f32, 10);
508        for npc_position in mob_positions_pit {
509            let entities = [
510                "common.entity.dungeon.myrmidon.hoplite",
511                "common.entity.dungeon.myrmidon.marksman",
512                "common.entity.dungeon.myrmidon.strategian",
513            ];
514            let npc_pos = npc_position.with_z(base - 20);
515            let npc = entities[(RandomField::new(0).get(npc_pos) % entities.len() as u32) as usize];
516            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(npc, &mut rng, None));
517        }
518        let mob_positions_high = place_circular(center, radius as f32, 20);
519        for npc_position in mob_positions_high {
520            let npc_pos = npc_position.with_z(base + top_platform_height + 5);
521            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
522                "common.entity.dungeon.myrmidon.marksman",
523                &mut rng,
524                None,
525            ));
526        }
527
528        let boss_pos = self.arena_data.boss_pos;
529        let npc_pos = Vec2::new(boss_pos.x - 50, boss_pos.y).with_z(base - 20);
530        // boss chamber
531        painter
532            .aabb(Aabb {
533                min: (npc_pos - 12).with_z(base - 21),
534                max: (npc_pos + 12).with_z(base - 12),
535            })
536            .fill(sandstone_unbroken.clone());
537        painter
538            .vault(
539                Aabb {
540                    min: Vec2::new(boss_pos.x - 4, boss_pos.y - 10).with_z(base - 22),
541                    max: Vec2::new(boss_pos.x + 4, boss_pos.y + 18).with_z(base - 12),
542                },
543                Dir::Y,
544            )
545            .clear();
546        painter
547            .vault(
548                Aabb {
549                    min: Vec2::new(boss_pos.x - 52, boss_pos.y - 4).with_z(base - 22),
550                    max: Vec2::new(boss_pos.x, boss_pos.y + 4).with_z(base - 12),
551                },
552                Dir::X,
553            )
554            .clear();
555        painter
556            .vault(
557                Aabb {
558                    min: Vec2::new(boss_pos.x - 4, boss_pos.y - 4).with_z(base - 22),
559                    max: Vec2::new(boss_pos.x - 3, boss_pos.y + 4).with_z(base - 12),
560                },
561                Dir::X,
562            )
563            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
564        painter
565            .aabb(Aabb {
566                min: Vec2::new(boss_pos.x - 4, boss_pos.y + 4).with_z(base - 18),
567                max: Vec2::new(boss_pos.x - 3, boss_pos.y + 17).with_z(base - 17),
568            })
569            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
570        painter
571            .vault(
572                Aabb {
573                    min: Vec2::new(boss_pos.x - 4, boss_pos.y + 17).with_z(base - 20),
574                    max: Vec2::new(boss_pos.x + 4, boss_pos.y + 18).with_z(base - 12),
575                },
576                Dir::Y,
577            )
578            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
579        painter
580            .vault(
581                Aabb {
582                    min: Vec2::new(boss_pos.x, boss_pos.y + 17).with_z(base - 18),
583                    max: Vec2::new(boss_pos.x + 1, boss_pos.y + 18).with_z(base - 17),
584                },
585                Dir::Y,
586            )
587            .fill(Fill::Block(Block::air(SpriteKind::MinotaurKeyhole)));
588
589        let npc_pos = Vec2::new(boss_pos.x - 50, boss_pos.y).with_z(base - 20);
590
591        painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
592            "common.entity.dungeon.myrmidon.minotaur",
593            &mut rng,
594            None,
595        ));
596        // solid floor
597        painter
598            .cylinder(Aabb {
599                min: (center - 3 * (radius / 4)).with_z(base - 25),
600                max: (center + 3 * (radius / 4)).with_z(base - 21),
601            })
602            .fill(sandstone_unbroken.clone());
603        // catacomb
604        painter
605            .cylinder(Aabb {
606                min: (center - 3 * (radius / 4)).with_z(base - 79),
607                max: (center + 3 * (radius / 4)).with_z(base - 25),
608            })
609            .fill(weak_sandstone.clone());
610        // entry
611        painter
612            .cylinder(Aabb {
613                min: (center - 5).with_z(base - 79),
614                max: (center + 1).with_z(base - 18),
615            })
616            .fill(sandstone_unbroken.clone());
617        painter
618            .cylinder(Aabb {
619                min: (center - 4).with_z(base - 28),
620                max: center.with_z(base - 14),
621            })
622            .fill(sandstone_unbroken.clone());
623        painter
624            .cylinder(Aabb {
625                min: (center - 4).with_z(base - 14),
626                max: center.with_z(base - 13),
627            })
628            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
629        painter
630            .cylinder(Aabb {
631                min: (center - 3).with_z(base - 77),
632                max: (center - 1).with_z(base - 13),
633            })
634            .clear();
635        painter
636            .aabb(Aabb {
637                min: (center - 3).with_z(base - 43),
638                max: (center + 5).with_z(base - 33),
639            })
640            .clear();
641        painter
642            .cylinder(Aabb {
643                min: (center - 10).with_z(base - 79),
644                max: (center + 5).with_z(base - 77),
645            })
646            .clear();
647
648        for dir in NEIGHBORS {
649            for r in 1..=3 {
650                let room_pos = center + dir * ((radius / 6) * r);
651
652                for s in 0..3 {
653                    let room_var = RandomField::new(0).get(room_pos.with_z(base + r + s)) % 6;
654                    let room_dir = if room_var < 3 { Dir::Y } else { Dir::X };
655                    let room = painter.vault(
656                        Aabb {
657                            min: (room_pos - (radius / 8)).with_z(base - 79 + (18 * s)),
658                            max: (room_pos + (radius / 8)).with_z(base - 64 + (18 * s)),
659                        },
660                        room_dir,
661                    );
662
663                    match room_var {
664                        0 => room.fill(weak_sandstone.clone()),
665                        _ => room.clear(),
666                    };
667
668                    // storey exit clear
669                    if room_var > 3 {
670                        painter
671                            .vault(
672                                Aabb {
673                                    min: (room_pos - (radius / 8)).with_z(base - 79 + (18 * s)),
674                                    max: (room_pos + (radius / 8)).with_z(base - 59 + (18 * s)),
675                                },
676                                room_dir,
677                            )
678                            .clear();
679                    }
680                }
681            }
682        }
683        let cyclops_pos_high = Vec2::new(center.x, center.y - 14).with_z(base - 40);
684        painter
685            .vault(
686                Aabb {
687                    min: (cyclops_pos_high - (radius / 8)).with_z(base - 40),
688                    max: (cyclops_pos_high + (radius / 8)).with_z(base - 25),
689                },
690                Dir::X,
691            )
692            .clear();
693        painter.spawn(EntityInfo::at(cyclops_pos_high.as_()).with_asset_expect(
694            "common.entity.dungeon.myrmidon.cyclops_key",
695            &mut rng,
696            None,
697        ));
698        let cyclops_pos_low = Vec2::new(center.x, center.y + 14).with_z(base - 79);
699        painter
700            .vault(
701                Aabb {
702                    min: (cyclops_pos_low - (radius / 8)).with_z(base - 79),
703                    max: (cyclops_pos_low + (radius / 8)).with_z(base - 64),
704                },
705                Dir::X,
706            )
707            .clear();
708        painter.spawn(EntityInfo::at(cyclops_pos_low.as_()).with_asset_expect(
709            "common.entity.dungeon.myrmidon.cyclops",
710            &mut rng,
711            None,
712        ));
713        let mob_positions_cellar = place_circular(center, 2.0, 3);
714        for npc_position in mob_positions_cellar {
715            let npc_pos = npc_position.with_z(base - 79);
716            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
717                "common.entity.dungeon.myrmidon.marksman",
718                &mut rng,
719                None,
720            ));
721        }
722    }
723}