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
20pub struct MyrmidonArena {
22 bounds: Aabr<i32>,
24 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}