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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}