Skip to main content

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