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