1use super::*;
2use crate::{
3 Land,
4 site::generation::{inscribed_polystar, place_circular},
5 util::{DIAGONALS, RandomField, sampler::Sampler, within_distance},
6};
7use common::{
8 comp::misc::PortalData,
9 generation::{EntityInfo, SpecialEntity},
10 resources::Secs,
11 terrain::SpriteKind,
12};
13use rand::prelude::*;
14use std::sync::Arc;
15use vek::*;
16
17pub struct Room {
18 room_base: i32,
19 room_center: Vec2<i32>,
20 clear_center: Vec2<i32>,
21 mob_room: bool,
22 boss_room: bool,
23 portal_to_boss: bool,
24}
25
26pub struct Cultist {
27 base: i32,
28 bounds: Aabr<i32>,
29 pub(crate) alt: i32,
30 pub(crate) center: Vec2<i32>,
31 pub(crate) room_data: Vec<Room>,
32 room_size: i32,
33 floors: i32,
34}
35impl Cultist {
36 pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
37 let bounds = Aabr {
38 min: site.tile_wpos(tile_aabr.min),
39 max: site.tile_wpos(tile_aabr.max),
40 };
41 let center = bounds.center();
42 let base = land.get_alt_approx(center) as i32;
43 let room_size = 30;
44 let mut room_data = vec![];
45
46 let floors = 3;
47 for f in 0..=floors {
48 for s in 0..=1 {
49 let rooms = [1, 2];
51 let boss_portal_floor =
52 (1 + (RandomField::new(0).get(center.with_z(base + 1)) % 2)) as i32;
53 let portal_to_boss_index =
54 (RandomField::new(0).get(center.with_z(base)) % 4) as usize;
55 if rooms.contains(&f) {
56 for (d, dir) in DIAGONALS.iter().enumerate() {
57 let room_base = base - (f * (2 * (room_size))) - (s * room_size);
58 let room_center = center + (dir * ((room_size * 2) - 5 + (10 * s)));
59 let clear_center = center + (dir * ((room_size * 2) - 6 + (10 * s)));
60 let mob_room = s < 1;
61 let portal_to_boss =
62 mob_room && d == portal_to_boss_index && f == boss_portal_floor;
63 room_data.push(Room {
64 room_base,
65 room_center,
66 clear_center,
67 mob_room,
68 boss_room: false,
69 portal_to_boss,
70 });
71 }
72 }
73 }
74 }
75 let boss_room_base = base - (6 * room_size);
76 room_data.push(Room {
77 room_base: boss_room_base,
78 room_center: center,
79 clear_center: center,
80 mob_room: false,
81 boss_room: true,
82 portal_to_boss: false,
83 });
84
85 Self {
86 bounds,
87 alt: land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2))
88 as i32
89 + 2,
90 base,
91 center,
92 room_size,
93 room_data,
94 floors,
95 }
96 }
97}
98
99impl Structure for Cultist {
100 #[cfg(feature = "dyn-lib")]
101 #[unsafe(export_name = "as_dyn_structure_cultist")]
102 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
103 Some((Self::as_dyn_impl(self), "as_dyn_structure_cultist"))
104 }
105
106 fn spawn_rules_inner(
107 &self,
108 spawn_rules: &mut SpawnRules,
109 _land: &Land,
110 wpos: Vec2<i32>,
111 _weight: f32,
112 ) {
113 spawn_rules.trees &= !within_distance(wpos, self.bounds.center(), 75);
114 spawn_rules.waypoints = false;
115 }
116
117 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
118 let center = self.center;
119 let base = self.base;
120 let room_size = self.room_size;
121 let floors = self.floors;
122 let mut rng = rand::rng();
123 let candles_lite = Fill::Sampling(Arc::new(|wpos| {
124 Some(match (RandomField::new(0).get(wpos)) % 30 {
125 0 => Block::air(SpriteKind::Candle),
126 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
127 })
128 }));
129
130 let mut tower_positions = vec![];
131 let mut clear_positions = vec![];
132 let room_data = &self.room_data;
133 let mut star_positions = vec![];
134 let mut sprite_positions = vec![];
135 let mut random_npcs = vec![];
136
137 let rock_broken = Fill::Sampling(Arc::new(|center| {
138 Some(match (RandomField::new(0).get(center)) % 52 {
139 0..=8 => Block::new(BlockKind::Rock, Rgb::new(60, 55, 65)),
140 9..=17 => Block::new(BlockKind::Rock, Rgb::new(65, 60, 70)),
141 18..=26 => Block::new(BlockKind::Rock, Rgb::new(70, 65, 75)),
142 27..=35 => Block::new(BlockKind::Rock, Rgb::new(75, 70, 80)),
143 36..=37 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
144 _ => Block::new(BlockKind::Rock, Rgb::new(55, 50, 60)),
145 })
146 }));
147 let rock = Fill::Brick(BlockKind::Rock, Rgb::new(55, 50, 60), 24);
148 let water = Fill::Block(Block::new(BlockKind::Water, Rgb::zero()));
149 let key_door = Fill::Block(Block::air(SpriteKind::KeyDoor));
150 let key_hole = Fill::Block(Block::air(SpriteKind::Keyhole));
151 let gold_chain = Fill::Block(Block::air(SpriteKind::SeaDecorChain));
152
153 for room in room_data {
154 let (room_base, room_center, mob_room) =
155 (room.room_base, room.room_center, room.mob_room);
156 painter
159 .aabb(Aabb {
160 min: (room_center - room_size - 23).with_z(room_base - room_size - 1),
161 max: (room_center + room_size + 23).with_z(room_base - 2),
162 })
163 .fill(rock.clone());
164 if mob_room {
165 painter
166 .aabb(Aabb {
167 min: (room_center - room_size - 2).with_z(room_base - room_size - 1),
168 max: (room_center + room_size + 2).with_z(room_base - 2),
169 })
170 .fill(rock_broken.clone());
171 }
172 painter
174 .aabb(Aabb {
175 min: (room_center - room_size - 10).with_z(room_base - room_size - 2),
176 max: (room_center + room_size + 10).with_z(room_base - room_size - 1),
177 })
178 .fill(rock.clone());
179 painter
181 .cylinder(Aabb {
182 min: (room_center - room_size + 1).with_z(room_base - room_size - 1),
183 max: (room_center + room_size - 1).with_z(room_base - room_size),
184 })
185 .fill(candles_lite.clone());
186 }
187
188 for s in 0..=floors {
189 let room_base = base - (s * (2 * room_size));
190
191 for p in 3..=5 {
193 let pos = 3 * p;
194 let radius = pos * 2;
195 let amount = pos;
196 let clear_radius = radius - 8;
197 let tower_pos = place_circular(center, radius as f32, amount);
198 let clear_pos = place_circular(center, clear_radius as f32, amount);
199 tower_positions.extend(tower_pos);
200 clear_positions.extend(clear_pos);
201 }
202 for tower_center in &tower_positions {
203 let height_var =
204 (RandomField::new(0).get(tower_center.with_z(room_base)) % 15) as i32;
205 let height = height_var * 3;
206 let size = height_var / 3;
207
208 if room_base < base {
212 painter
213 .aabb(Aabb {
214 min: (tower_center - 9 - size).with_z(room_base - 2),
215 max: (tower_center + 9 + size).with_z(room_base + 10 + height),
216 })
217 .fill(rock.clone());
218 painter
219 .aabb(Aabb {
220 min: (tower_center - 9 - (size / 2)).with_z(room_base + 8 + height),
221 max: (tower_center + 9 + (size / 2))
222 .with_z(room_base + 10 + height + 5 + (height / 2)),
223 })
224 .fill(rock.clone());
225 }
226 painter
227 .aabb(Aabb {
228 min: (tower_center - 8 - size).with_z(room_base),
229 max: (tower_center + 8 + size).with_z(room_base + 10 + height),
230 })
231 .fill(rock_broken.clone());
232 painter
233 .aabb(Aabb {
234 min: (tower_center - 8 - (size / 2)).with_z(room_base + 10 + height),
235 max: (tower_center + 8 + (size / 2))
236 .with_z(room_base + 10 + height + 5 + (height / 2)),
237 })
238 .fill(rock_broken.clone());
239
240 painter
242 .vault(
243 Aabb {
244 min: Vec2::new(tower_center.x - 8 - size, tower_center.y - 4 - size)
245 .with_z(room_base + size),
246 max: Vec2::new(tower_center.x + 8 + size, tower_center.y + 4 + size)
247 .with_z(room_base + height),
248 },
249 Dir2::X,
250 )
251 .clear();
252
253 painter
254 .vault(
255 Aabb {
256 min: Vec2::new(tower_center.x - 4 - size, tower_center.y - 8 - size)
257 .with_z(room_base + size),
258 max: Vec2::new(tower_center.x + 4 + size, tower_center.y + 8 + size)
259 .with_z(room_base + height),
260 },
261 Dir2::Y,
262 )
263 .clear();
264 painter
266 .vault(
267 Aabb {
268 min: Vec2::new(
269 tower_center.x - 8 - (size / 2),
270 tower_center.y - 4 - (size / 2),
271 )
272 .with_z(room_base + 10 + height),
273 max: Vec2::new(
274 tower_center.x + 8 + (size / 2),
275 tower_center.y + 4 + (size / 2),
276 )
277 .with_z(room_base + 10 + height + 5 + (height / 4) + (size / 2)),
278 },
279 Dir2::X,
280 )
281 .clear();
282
283 painter
284 .vault(
285 Aabb {
286 min: Vec2::new(
287 tower_center.x - 4 - (size / 2),
288 tower_center.y - 8 - (size / 2),
289 )
290 .with_z(room_base + 10 + height),
291 max: Vec2::new(
292 tower_center.x + 4 + (size / 2),
293 tower_center.y + 8 + (size / 2),
294 )
295 .with_z(room_base + 10 + height + 5 + (height / 4) + (size / 2)),
296 },
297 Dir2::Y,
298 )
299 .clear();
300 }
301 for (tower_center, clear_center) in tower_positions.iter().zip(&clear_positions) {
303 let height_var =
304 (RandomField::new(0).get(tower_center.with_z(room_base)) % 15) as i32;
305 let height = height_var * 3;
306 let size = height_var / 3;
307 painter
309 .aabb(Aabb {
310 min: (clear_center - 9 - size).with_z(room_base + size),
311 max: (clear_center + 9 + size).with_z(room_base + 8 + height),
312 })
313 .clear();
314 painter
315 .aabb(Aabb {
316 min: (clear_center - 8 - (size / 2)).with_z(room_base + 10 + height),
317 max: (clear_center + 8 + (size / 2))
318 .with_z(room_base + 8 + height + 5 + (height / 2)),
319 })
320 .clear();
321
322 let decay_size = 8 + size;
324 painter
325 .cylinder(Aabb {
326 min: (clear_center - decay_size).with_z(room_base + 8 + height),
327 max: (clear_center + decay_size).with_z(room_base + 10 + height),
328 })
329 .clear();
330
331 painter
332 .cylinder(Aabb {
333 min: (clear_center - decay_size + 5)
334 .with_z(room_base + 8 + height + 5 + (height / 2)),
335 max: (clear_center + decay_size - 5)
336 .with_z(room_base + 10 + height + 5 + (height / 2)),
337 })
338 .clear();
339 }
340
341 painter
343 .cylinder(Aabb {
344 min: (center - room_size + 10).with_z(room_base),
345 max: (center + room_size - 10).with_z(room_base + (4 * (room_size))),
346 })
347 .clear();
348 }
349 for room in room_data {
351 let (room_base, room_center, clear_center, mob_room, boss_room, portal_to_boss) = (
352 room.room_base,
353 room.room_center,
354 room.clear_center,
355 room.mob_room,
356 room.boss_room,
357 room.portal_to_boss,
358 );
359 painter
360 .cylinder(Aabb {
361 min: (clear_center - room_size - 1).with_z(room_base - room_size),
362 max: (clear_center + room_size + 1).with_z(room_base - 4),
363 })
364 .clear();
365
366 let decor_var = RandomField::new(0).get(room_center.with_z(room_base)) % 4;
368 if mob_room {
369 if decor_var < 3 {
371 painter
372 .aabb(Aabb {
373 min: (room_center - room_size + 10).with_z(room_base - room_size - 1),
374 max: (room_center + room_size - 10).with_z(room_base - 2),
375 })
376 .fill(rock_broken.clone());
377 }
378
379 let spacing = 12;
381 let carve_length = room_size + 8;
382 let carve_width = 3;
383 for f in 0..3 {
384 for c in 0..5 {
385 let sprite_pos_1 = Vec2::new(
387 room_center.x - room_size + (spacing / 2) + (spacing * c) - carve_width
388 + 2,
389 room_center.y - carve_length + 2,
390 )
391 .with_z(room_base - room_size - 1 + ((room_size / 3) * f));
392 sprite_positions.push(sprite_pos_1);
393
394 let sprite_pos_2 = Vec2::new(
395 room_center.x - room_size + (spacing / 2) + (spacing * c) + carve_width
396 - 2,
397 room_center.y + carve_length - 2,
398 )
399 .with_z(room_base - room_size - 1 + ((room_size / 3) * f));
400 sprite_positions.push(sprite_pos_2);
401
402 let sprite_pos_3 = Vec2::new(
403 room_center.x - carve_length + 2,
404 room_center.y - room_size + (spacing / 2) + (spacing * c) - carve_width
405 + 2,
406 )
407 .with_z(room_base - room_size - 1 + ((room_size / 3) * f));
408 sprite_positions.push(sprite_pos_3);
409
410 let sprite_pos_4 = Vec2::new(
411 room_center.x + carve_length - 2,
412 room_center.y - room_size + (spacing / 2) + (spacing * c) + carve_width
413 - 2,
414 )
415 .with_z(room_base - room_size - 1 + ((room_size / 3) * f));
416 sprite_positions.push(sprite_pos_4);
417
418 let candle_limiter = painter.aabb(Aabb {
419 min: (room_center - room_size + 10)
420 .with_z(room_base - room_size - 2 + ((room_size / 3) * f)),
421 max: (room_center + room_size - 10)
422 .with_z(room_base - room_size + ((room_size / 3) * f)),
423 });
424
425 painter
426 .vault(
427 Aabb {
428 min: Vec2::new(
429 room_center.x - room_size + (spacing / 2) + (spacing * c)
430 - carve_width,
431 room_center.y - carve_length,
432 )
433 .with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
434 max: Vec2::new(
435 room_center.x - room_size
436 + (spacing / 2)
437 + (spacing * c)
438 + carve_width,
439 room_center.y + carve_length,
440 )
441 .with_z(
442 room_base - room_size - 3
443 + (room_size / 3)
444 + ((room_size / 3) * f),
445 ),
446 },
447 Dir2::Y,
448 )
449 .clear();
450
451 painter
452 .aabb(Aabb {
453 min: Vec2::new(
454 room_center.x - room_size + (spacing / 2) + (spacing * c)
455 - carve_width,
456 room_center.y - carve_length,
457 )
458 .with_z(room_base - room_size - 2 + ((room_size / 3) * f)),
459 max: Vec2::new(
460 room_center.x - room_size
461 + (spacing / 2)
462 + (spacing * c)
463 + carve_width,
464 room_center.y + carve_length,
465 )
466 .with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
467 })
468 .intersect(candle_limiter)
469 .fill(rock.clone());
470 painter
471 .aabb(Aabb {
472 min: Vec2::new(
473 room_center.x - room_size + (spacing / 2) + (spacing * c)
474 - carve_width,
475 room_center.y - carve_length,
476 )
477 .with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
478 max: Vec2::new(
479 room_center.x - room_size
480 + (spacing / 2)
481 + (spacing * c)
482 + carve_width,
483 room_center.y + carve_length,
484 )
485 .with_z(room_base - room_size + ((room_size / 3) * f)),
486 })
487 .intersect(candle_limiter)
488 .fill(candles_lite.clone());
489
490 painter
491 .vault(
492 Aabb {
493 min: Vec2::new(
494 room_center.x - carve_length,
495 room_center.y - room_size + (spacing / 2) + (spacing * c)
496 - carve_width,
497 )
498 .with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
499 max: Vec2::new(
500 room_center.x + carve_length,
501 room_center.y - room_size
502 + (spacing / 2)
503 + (spacing * c)
504 + carve_width,
505 )
506 .with_z(
507 room_base - room_size - 3
508 + (room_size / 3)
509 + ((room_size / 3) * f),
510 ),
511 },
512 Dir2::X,
513 )
514 .clear();
515
516 painter
517 .aabb(Aabb {
518 min: Vec2::new(
519 room_center.x - carve_length,
520 room_center.y - room_size + (spacing / 2) + (spacing * c)
521 - carve_width,
522 )
523 .with_z(room_base - room_size - 2 + ((room_size / 3) * f)),
524 max: Vec2::new(
525 room_center.x + carve_length,
526 room_center.y - room_size
527 + (spacing / 2)
528 + (spacing * c)
529 + carve_width,
530 )
531 .with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
532 })
533 .intersect(candle_limiter)
534 .fill(rock.clone());
535 painter
536 .aabb(Aabb {
537 min: Vec2::new(
538 room_center.x - carve_length,
539 room_center.y - room_size + (spacing / 2) + (spacing * c)
540 - carve_width,
541 )
542 .with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
543 max: Vec2::new(
544 room_center.x + carve_length,
545 room_center.y - room_size
546 + (spacing / 2)
547 + (spacing * c)
548 + carve_width,
549 )
550 .with_z(room_base - room_size + ((room_size / 3) * f)),
551 })
552 .intersect(candle_limiter)
553 .fill(candles_lite.clone());
554 }
555 for dir in CARDINALS {
557 for d in 1..=4 {
558 let npc_pos = (room_center + dir * ((spacing / 2) * d))
559 .with_z(room_base - room_size + ((room_size / 3) * f));
560 let pos_var = RandomField::new(0).get(npc_pos) % 10;
561 if pos_var < 2 {
562 painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
563 "common.entity.dungeon.cultist.cultist",
564 &mut rng,
565 None,
566 ))
567 } else if pos_var > 2 && f > 0 {
568 painter.sphere_with_radius(npc_pos, 5_f32).clear();
569 }
570 }
571 }
572 let decor_var = RandomField::new(0).get(room_center.with_z(room_base)) % 4;
573 if decor_var < 3 {
574 painter
576 .cylinder(Aabb {
577 min: (room_center - 3).with_z(room_base - (room_size / 4) - 5),
578 max: (room_center + 3).with_z(room_base - (room_size / 4) - 4),
579 })
580 .fill(rock.clone());
581 } else {
582 painter
583 .cylinder(Aabb {
584 min: (room_center - room_size + 10)
585 .with_z(room_base - room_size - 3),
586 max: (room_center + room_size - 10)
587 .with_z(room_base - room_size - 2),
588 })
589 .fill(rock.clone());
590 painter
591 .cylinder(Aabb {
592 min: (room_center - room_size + 10)
593 .with_z(room_base - room_size - 2),
594 max: (room_center + room_size - 10)
595 .with_z(room_base - room_size - 1),
596 })
597 .fill(water.clone());
598 painter
599 .aabb(Aabb {
600 min: (room_center - room_size + 10)
601 .with_z(room_base - room_size - 1),
602 max: (room_center + room_size - 10)
603 .with_z(room_base - (room_size / 4) - 3),
604 })
605 .clear();
606 }
607 }
608 }
609 let mob_portal = room_center.with_z(room_base - (room_size / 4));
611 let mob_portal_target = (room_center + 10).with_z(room_base - (room_size * 2));
612 let mini_boss_portal = room_center.with_z(room_base - (room_size * 2));
613 let exit_position = (center - 10).with_z(base - (6 * room_size));
614 let boss_position = (center - 10).with_z(base - (7 * room_size));
615 let boss_portal = center.with_z(base - (7 * room_size));
616 let mini_boss_portal_target = if portal_to_boss {
617 boss_position.as_::<f32>()
618 } else {
619 exit_position.as_::<f32>()
620 };
621 if mob_room {
622 painter.spawn(EntityInfo::at(mob_portal.as_::<f32>()).into_special(
623 SpecialEntity::Teleporter(PortalData {
624 target: mob_portal_target.as_::<f32>(),
625 requires_no_aggro: true,
626 buildup_time: Secs(5.),
627 }),
628 ));
629 painter.spawn(EntityInfo::at(mini_boss_portal.as_::<f32>()).into_special(
630 SpecialEntity::Teleporter(PortalData {
631 target: mini_boss_portal_target,
632 requires_no_aggro: true,
633 buildup_time: Secs(5.),
634 }),
635 ));
636 } else if boss_room {
637 painter.spawn(EntityInfo::at(boss_portal.as_::<f32>()).into_special(
638 SpecialEntity::Teleporter(PortalData {
639 target: exit_position.as_::<f32>(),
640 requires_no_aggro: true,
641 buildup_time: Secs(5.),
642 }),
643 ));
644 }
645
646 if !mob_room {
647 if boss_room {
648 let npc_pos = room_center.with_z(room_base - room_size);
649
650 painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
651 "common.entity.dungeon.cultist.mindflayer",
652 &mut rng,
653 None,
654 ));
655 } else {
656 let npc_pos = (room_center - 2).with_z(room_base - room_size);
657 painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
658 "common.entity.dungeon.cultist.warlock",
659 &mut rng,
660 None,
661 ));
662
663 painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
664 "common.entity.dungeon.cultist.warlord",
665 &mut rng,
666 None,
667 ));
668 painter.spawn(
669 EntityInfo::at(((room_center + 5).with_z(room_base - room_size)).as_())
670 .with_asset_expect(
671 "common.entity.dungeon.cultist.beastmaster",
672 &mut rng,
673 None,
674 ),
675 );
676 }
677 let chain_positions = place_circular(room_center, 15.0, 10);
679 for pos in chain_positions {
680 painter
681 .aabb(Aabb {
682 min: pos.with_z(room_base - 12),
683 max: (pos + 1).with_z(room_base - 4),
684 })
685 .fill(gold_chain.clone());
686 }
687 }
688 let down = if mob_room && decor_var < 3 {
689 0
690 } else if mob_room && decor_var > 2 {
691 room_size
692 } else {
693 10
694 };
695 let magic_circle_bb = painter.cylinder(Aabb {
696 min: (room_center - 15).with_z(room_base - 3 - down),
697 max: (room_center + 16).with_z(room_base - 2 - down),
698 });
699 star_positions.push((magic_circle_bb, room_center));
700 }
701 for sprite_pos in sprite_positions {
703 if !within_distance(sprite_pos.xy(), center, 40)
705 || sprite_pos.z < (base - (6 * room_size))
706 {
707 match (RandomField::new(0).get(sprite_pos + 1)) % 16 {
708 0 => {
709 if sprite_pos.z > (base - (6 * room_size)) {
710 random_npcs.push(sprite_pos)
711 }
712 },
713 1 => {
714 painter
716 .aabb(Aabb {
717 min: (sprite_pos - 1).with_z(sprite_pos.z),
718 max: (sprite_pos + 2).with_z(sprite_pos.z + 3),
719 })
720 .fill(key_door.clone());
721 painter
722 .aabb(Aabb {
723 min: sprite_pos.with_z(sprite_pos.z + 3),
724 max: (sprite_pos + 1).with_z(sprite_pos.z + 4),
725 })
726 .fill(key_hole.clone());
727 painter
728 .aabb(Aabb {
729 min: (sprite_pos).with_z(sprite_pos.z),
730 max: (sprite_pos + 1).with_z(sprite_pos.z + 2),
731 })
732 .clear();
733 painter.spawn(EntityInfo::at(sprite_pos.as_()).with_asset_expect(
734 match (RandomField::new(0).get(sprite_pos)) % 10 {
735 0 => "common.entity.village.farmer",
736 1 => "common.entity.village.guard",
737 2 => "common.entity.village.hunter",
738 3 => "common.entity.village.skinner",
739 _ => "common.entity.village.villager",
740 },
741 &mut rng,
742 None,
743 ));
744 },
745 _ => {
746 painter.sprite(
747 sprite_pos,
748 match (RandomField::new(0).get(sprite_pos)) % 20 {
749 0 => SpriteKind::DungeonChest5,
750 _ => SpriteKind::Candle,
751 },
752 );
753 },
754 }
755 }
756 }
757 for s in 1..floors {
759 let room_base = base - (s * (2 * room_size));
760 let passage_height = room_base - room_size - 1;
761
762 painter
763 .cylinder(Aabb {
764 min: (center - 22).with_z(passage_height - 4),
765 max: (center + 23).with_z(passage_height - 1),
766 })
767 .fill(rock_broken.clone());
768
769 for dir in DIAGONALS {
770 let clear_pos = center + dir * 30;
771 painter
772 .line(
773 center.with_z(passage_height),
774 clear_pos.with_z(passage_height),
775 5.0,
776 )
777 .fill(rock_broken.clone());
778 painter
779 .line(
780 center.with_z(passage_height + 2),
781 clear_pos.with_z(passage_height + 2),
782 5.0,
783 )
784 .clear();
785 }
786
787 painter
788 .cylinder(Aabb {
789 min: (center - 15).with_z(passage_height - 4),
790 max: (center + 16).with_z(passage_height + 1),
791 })
792 .clear();
793
794 let magic_circle_passage = painter.cylinder(Aabb {
795 min: (center - 15).with_z(passage_height - 3),
796 max: (center + 16).with_z(passage_height - 2),
797 });
798 star_positions.push((magic_circle_passage, center));
799 }
800 for s in 0..=1 {
802 let radius = 62.0 - (s * 50) as f32;
803 let npcs = place_circular(center, radius, 8 - (s * 4));
804 for npc_pos in npcs {
805 random_npcs.push(npc_pos.with_z(base + 8 - ((6 * room_size) * s) - (s * 8)));
806 }
807 }
808 for pos in random_npcs {
809 let entities = [
810 "common.entity.dungeon.cultist.cultist",
811 "common.entity.dungeon.cultist.turret",
812 "common.entity.dungeon.cultist.husk",
813 "common.entity.dungeon.cultist.husk_brute",
814 "common.entity.dungeon.cultist.hound",
815 ];
816 let npc = entities[(RandomField::new(0).get(pos) % entities.len() as u32) as usize];
817 painter.spawn(EntityInfo::at(pos.as_()).with_asset_expect(npc, &mut rng, None));
818 }
819
820 let top_position = (center - 20).with_z(base + 125);
822 let bottom_position = center.with_z(base - (6 * room_size));
823 let top_pos = Vec3::new(
824 top_position.x as f32,
825 top_position.y as f32,
826 top_position.z as f32,
827 );
828 let bottom_pos = Vec3::new(
829 bottom_position.x as f32,
830 bottom_position.y as f32,
831 bottom_position.z as f32,
832 );
833 painter.spawn(
834 EntityInfo::at(bottom_pos).into_special(SpecialEntity::Teleporter(PortalData {
835 target: top_pos,
836 requires_no_aggro: true,
837 buildup_time: Secs(5.),
838 })),
839 );
840 let stone_purple = Block::new(BlockKind::GlowingRock, Rgb::new(96, 0, 128));
841 let magic_circle_bb = painter.cylinder(Aabb {
842 min: (center - 15).with_z(base - (floors * (2 * room_size)) - 1),
843 max: (center + 16).with_z(base - (floors * (2 * room_size))),
844 });
845 let magic_circle_bb_boss = painter.cylinder(Aabb {
846 min: (center - 15).with_z(base - (7 * room_size) - 2),
847 max: (center + 16).with_z(base - (7 * room_size) - 1),
848 });
849 star_positions.push((magic_circle_bb, center));
850 star_positions.push((magic_circle_bb_boss, center));
851 for (magic_circle_bb, position) in star_positions {
852 let magic_circle = painter.prim(Primitive::sampling(
853 magic_circle_bb,
854 inscribed_polystar(position, 15.0, 7),
855 ));
856 painter.fill(magic_circle, Fill::Block(stone_purple));
857 }
858 painter
860 .cylinder(Aabb {
861 min: (center - room_size - 15).with_z(base - (floors * (2 * room_size)) - 3),
862 max: (center + room_size + 15).with_z(base - (floors * (2 * room_size)) - 2),
863 })
864 .fill(rock.clone());
865 }
866}