veloren_world/site2/plot/
myrmidon_arena.rs

1use super::*;
2use crate::{
3    Land,
4    site2::gen::place_circular,
5    util::{NEIGHBORS, RandomField, Sampler},
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: wpos.distance_squared(self.bounds.center()) > (85_i32).pow(2),
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", 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 thread_rng = thread_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(
506                npc,
507                &mut thread_rng,
508                None,
509            ));
510        }
511        let mob_positions_pit = place_circular(center, (radius / 3) as f32, 10);
512        for npc_position in mob_positions_pit {
513            let entities = [
514                "common.entity.dungeon.myrmidon.hoplite",
515                "common.entity.dungeon.myrmidon.marksman",
516                "common.entity.dungeon.myrmidon.strategian",
517            ];
518            let npc_pos = npc_position.with_z(base - 20);
519            let npc = entities[(RandomField::new(0).get(npc_pos) % entities.len() as u32) as usize];
520            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
521                npc,
522                &mut thread_rng,
523                None,
524            ));
525        }
526        let mob_positions_high = place_circular(center, radius as f32, 20);
527        for npc_position in mob_positions_high {
528            let npc_pos = npc_position.with_z(base + top_platform_height + 5);
529            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
530                "common.entity.dungeon.myrmidon.marksman",
531                &mut thread_rng,
532                None,
533            ));
534        }
535
536        let boss_pos = self.arena_data.boss_pos;
537        let npc_pos = Vec2::new(boss_pos.x - 50, boss_pos.y).with_z(base - 20);
538        // boss chamber
539        painter
540            .aabb(Aabb {
541                min: (npc_pos - 12).with_z(base - 21),
542                max: (npc_pos + 12).with_z(base - 12),
543            })
544            .fill(sandstone_unbroken.clone());
545        painter
546            .vault(
547                Aabb {
548                    min: Vec2::new(boss_pos.x - 4, boss_pos.y - 10).with_z(base - 22),
549                    max: Vec2::new(boss_pos.x + 4, boss_pos.y + 18).with_z(base - 12),
550                },
551                Dir::Y,
552            )
553            .clear();
554        painter
555            .vault(
556                Aabb {
557                    min: Vec2::new(boss_pos.x - 52, boss_pos.y - 4).with_z(base - 22),
558                    max: Vec2::new(boss_pos.x, boss_pos.y + 4).with_z(base - 12),
559                },
560                Dir::X,
561            )
562            .clear();
563        painter
564            .vault(
565                Aabb {
566                    min: Vec2::new(boss_pos.x - 4, boss_pos.y - 4).with_z(base - 22),
567                    max: Vec2::new(boss_pos.x - 3, boss_pos.y + 4).with_z(base - 12),
568                },
569                Dir::X,
570            )
571            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
572        painter
573            .aabb(Aabb {
574                min: Vec2::new(boss_pos.x - 4, boss_pos.y + 4).with_z(base - 18),
575                max: Vec2::new(boss_pos.x - 3, boss_pos.y + 17).with_z(base - 17),
576            })
577            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
578        painter
579            .vault(
580                Aabb {
581                    min: Vec2::new(boss_pos.x - 4, boss_pos.y + 17).with_z(base - 20),
582                    max: Vec2::new(boss_pos.x + 4, boss_pos.y + 18).with_z(base - 12),
583                },
584                Dir::Y,
585            )
586            .fill(Fill::Block(Block::air(SpriteKind::MyrmidonKeyDoor)));
587        painter
588            .vault(
589                Aabb {
590                    min: Vec2::new(boss_pos.x, boss_pos.y + 17).with_z(base - 18),
591                    max: Vec2::new(boss_pos.x + 1, boss_pos.y + 18).with_z(base - 17),
592                },
593                Dir::Y,
594            )
595            .fill(Fill::Block(Block::air(SpriteKind::MinotaurKeyhole)));
596
597        let npc_pos = Vec2::new(boss_pos.x - 50, boss_pos.y).with_z(base - 20);
598
599        painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
600            "common.entity.dungeon.myrmidon.minotaur",
601            &mut thread_rng,
602            None,
603        ));
604
605        // catacomb
606        painter
607            .cylinder(Aabb {
608                min: (center - 3 * (radius / 4)).with_z(base - 79),
609                max: (center + 3 * (radius / 4)).with_z(base - 21),
610            })
611            .fill(weak_sandstone.clone());
612        // entry
613        painter
614            .cylinder(Aabb {
615                min: (center - 5).with_z(base - 79),
616                max: (center + 1).with_z(base - 18),
617            })
618            .fill(sandstone_unbroken.clone());
619        painter
620            .cylinder(Aabb {
621                min: (center - 4).with_z(base - 28),
622                max: center.with_z(base - 14),
623            })
624            .fill(sandstone_unbroken.clone());
625        painter
626            .cylinder(Aabb {
627                min: (center - 4).with_z(base - 14),
628                max: center.with_z(base - 13),
629            })
630            .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
631        painter
632            .cylinder(Aabb {
633                min: (center - 3).with_z(base - 77),
634                max: (center - 1).with_z(base - 13),
635            })
636            .clear();
637        painter
638            .aabb(Aabb {
639                min: (center - 3).with_z(base - 43),
640                max: (center + 5).with_z(base - 33),
641            })
642            .clear();
643        painter
644            .cylinder(Aabb {
645                min: (center - 10).with_z(base - 79),
646                max: (center + 5).with_z(base - 77),
647            })
648            .clear();
649
650        for dir in NEIGHBORS {
651            for r in 1..=3 {
652                let room_pos = center + dir * ((radius / 6) * r);
653
654                for s in 0..3 {
655                    let room_var = RandomField::new(0).get(room_pos.with_z(base + r + s)) % 6;
656                    let room_dir = if room_var < 3 { Dir::Y } else { Dir::X };
657                    let room = painter.vault(
658                        Aabb {
659                            min: (room_pos - (radius / 8)).with_z(base - 79 + (18 * s)),
660                            max: (room_pos + (radius / 8)).with_z(base - 64 + (18 * s)),
661                        },
662                        room_dir,
663                    );
664
665                    match room_var {
666                        0 => room.fill(weak_sandstone.clone()),
667                        _ => room.clear(),
668                    };
669
670                    // storey exit clear
671                    if room_var > 3 {
672                        painter
673                            .vault(
674                                Aabb {
675                                    min: (room_pos - (radius / 8)).with_z(base - 79 + (18 * s)),
676                                    max: (room_pos + (radius / 8)).with_z(base - 59 + (18 * s)),
677                                },
678                                room_dir,
679                            )
680                            .clear();
681                    }
682                }
683            }
684        }
685        let cyclops_pos_high = Vec2::new(center.x, center.y - 14).with_z(base - 40);
686        painter
687            .vault(
688                Aabb {
689                    min: (cyclops_pos_high - (radius / 8)).with_z(base - 40),
690                    max: (cyclops_pos_high + (radius / 8)).with_z(base - 25),
691                },
692                Dir::X,
693            )
694            .clear();
695        painter.spawn(EntityInfo::at(cyclops_pos_high.as_()).with_asset_expect(
696            "common.entity.dungeon.myrmidon.cyclops_key",
697            &mut thread_rng,
698            None,
699        ));
700        let cyclops_pos_low = Vec2::new(center.x, center.y + 14).with_z(base - 79);
701        painter
702            .vault(
703                Aabb {
704                    min: (cyclops_pos_low - (radius / 8)).with_z(base - 79),
705                    max: (cyclops_pos_low + (radius / 8)).with_z(base - 64),
706                },
707                Dir::X,
708            )
709            .clear();
710        painter.spawn(EntityInfo::at(cyclops_pos_low.as_()).with_asset_expect(
711            "common.entity.dungeon.myrmidon.cyclops",
712            &mut thread_rng,
713            None,
714        ));
715        let mob_positions_cellar = place_circular(center, 2.0, 3);
716        for npc_position in mob_positions_cellar {
717            let npc_pos = npc_position.with_z(base - 79);
718            painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
719                "common.entity.dungeon.myrmidon.marksman",
720                &mut thread_rng,
721                None,
722            ));
723        }
724    }
725}