1use super::*;
2use crate::{
3 Land,
4 site2::{
5 gen::{place_circular, place_circular_as_vec, spiral_staircase},
6 util::gradient::WrapMode,
7 },
8 util::{RandomField, sampler::Sampler},
9};
10use common::generation::EntityInfo;
11use rand::prelude::*;
12use std::sync::Arc;
13use vek::*;
14
15pub struct Sahagin {
16 bounds: Aabr<i32>,
17 pub(crate) alt: i32,
18 surface_color: Rgb<f32>,
19 sub_surface_color: Rgb<f32>,
20 pub(crate) center: Vec2<i32>,
21 pub(crate) rooms: Vec<Vec2<i32>>,
22 pub(crate) room_size: i32,
23}
24impl Sahagin {
25 pub fn generate(
26 land: &Land,
27 index: IndexRef,
28 _rng: &mut impl Rng,
29 site: &Site,
30 tile_aabr: Aabr<i32>,
31 ) -> Self {
32 let bounds = Aabr {
33 min: site.tile_wpos(tile_aabr.min),
34 max: site.tile_wpos(tile_aabr.max),
35 };
36 let (surface_color, sub_surface_color) =
37 if let Some(sample) = land.column_sample(bounds.center(), index) {
38 (sample.surface_color, sample.sub_surface_color)
39 } else {
40 (Rgb::new(161.0, 116.0, 86.0), Rgb::new(88.0, 64.0, 64.0))
41 };
42 let room_size = 30;
43 let center = bounds.center();
44 let outer_room_radius = (room_size * 2) + (room_size / 3);
45
46 let outer_rooms = place_circular(center, outer_room_radius as f32, 5);
47 let mut rooms = vec![center];
48 rooms.extend(outer_rooms);
49
50 Self {
51 bounds,
52 alt: CONFIG.sea_level as i32,
53 surface_color,
54 sub_surface_color,
55 center,
56 rooms,
57 room_size,
58 }
59 }
60
61 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
62 SpawnRules {
63 waypoints: false,
64 trees: wpos.distance_squared(self.bounds.center()) > (75_i32).pow(2),
65 ..SpawnRules::default()
66 }
67 }
68}
69
70impl Structure for Sahagin {
71 #[cfg(feature = "use-dyn-lib")]
72 const UPDATE_FN: &'static [u8] = b"render_sahagin\0";
73
74 #[cfg_attr(feature = "be-dyn-lib", export_name = "render_sahagin")]
75 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
76 let room_size = self.room_size;
77 let center = self.center;
78 let base = self.alt - room_size + 1;
79 let rooms = &self.rooms;
80 let mut thread_rng = thread_rng();
81 let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
82 let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
83 let gradient_center = Vec3::new(center.x as f32, center.y as f32, (base + 1) as f32);
84 let gradient_var_1 = RandomField::new(0).get(center.with_z(base)) as i32 % 8;
85 let gradient_var_2 = RandomField::new(0).get(center.with_z(base + 1)) as i32 % 10;
86 let mut random_npcs = vec![];
87 let brick = Fill::Gradient(
88 util::gradient::Gradient::new(
89 gradient_center,
90 8.0 + gradient_var_1 as f32,
91 util::gradient::Shape::Point,
92 (surface_color, sub_surface_color),
93 )
94 .with_repeat(if gradient_var_2 > 5 {
95 WrapMode::Repeat
96 } else {
97 WrapMode::PingPong
98 }),
99 BlockKind::Rock,
100 );
101 let jellyfish = Fill::Gradient(
102 util::gradient::Gradient::new(
103 gradient_center,
104 8.0 + gradient_var_1 as f32,
105 util::gradient::Shape::Point,
106 (Rgb::new(180, 181, 227), Rgb::new(120, 160, 255)),
107 )
108 .with_repeat(if gradient_var_2 > 5 {
109 WrapMode::Repeat
110 } else {
111 WrapMode::PingPong
112 }),
113 BlockKind::GlowingRock,
114 );
115 let white = Fill::Sampling(Arc::new(|center| {
116 Some(match (RandomField::new(0).get(center)) % 37 {
117 0..=8 => Block::new(BlockKind::Rock, Rgb::new(251, 251, 227)),
118 9..=17 => Block::new(BlockKind::Rock, Rgb::new(245, 245, 229)),
119 18..=26 => Block::new(BlockKind::Rock, Rgb::new(250, 243, 221)),
120 27..=35 => Block::new(BlockKind::Rock, Rgb::new(240, 240, 230)),
121 _ => Block::new(BlockKind::GlowingRock, Rgb::new(255, 244, 193)),
122 })
123 }));
124 let wood = Fill::Brick(BlockKind::Wood, Rgb::new(71, 33, 11), 12);
125 let key_door = Fill::Block(Block::air(SpriteKind::SahaginKeyDoor));
126 let key_hole = Fill::Block(Block::air(SpriteKind::SahaginKeyhole));
127 let rope = Fill::Block(Block::air(SpriteKind::Rope));
128 let room_size = 30;
129 let cell_size_raw = room_size / 6;
130 let ground_floor = base - (room_size * 2);
131 let outer_room_radius = (room_size * 2) + (room_size / 3);
132 let tunnel_radius = (room_size * 3) + 6;
133 let tunnel_points = place_circular_as_vec(center, tunnel_radius as f32, 25);
134 let scaler = -10;
135 let height_handle = -room_size;
136 let shell_radius = 3 * (room_size / 2) + scaler;
137 let shell_carve_radius = 6 * (room_size / 2) + scaler;
138 let shell_base = base + (room_size * 1) + height_handle;
139 let high_carve_base = base + (room_size * 7) + height_handle;
140 let low_carve_base = base + height_handle;
141 let shell_carve_limiter_1 = painter.aabb(Aabb {
142 min: (center - shell_radius - 6).with_z(shell_base),
143 max: (center + shell_radius + 6).with_z(shell_base + (5 * shell_radius)),
144 });
145
146 let shell_carve_limiter_2 = painter.aabb(Aabb {
147 min: (center - shell_radius).with_z(base + (room_size + 2) - 2),
148 max: (center + shell_radius).with_z(shell_base + (5 * shell_radius)),
149 });
150 painter
151 .cylinder_with_radius(
152 center.with_z(shell_base),
153 shell_radius as f32,
154 5.0 * shell_radius as f32,
155 )
156 .intersect(shell_carve_limiter_2)
157 .fill(white.clone());
158 let decor_radius = room_size / 3;
160 for b in 3..=7 {
161 let shell_decor = place_circular(center, (shell_radius - 2) as f32, 3 * b);
162
163 for pos in shell_decor {
164 let decor_var = 3 + RandomField::new(0).get(pos.with_z(base)) as i32 % 3;
165
166 painter
167 .sphere_with_radius(
168 pos.with_z(shell_base + (b * (shell_radius / 2))),
169 (decor_radius - decor_var) as f32,
170 )
171 .fill(white.clone());
172 }
173 }
174 painter
176 .sphere_with_radius(
177 (center - room_size).with_z(high_carve_base),
178 shell_carve_radius as f32,
179 )
180 .intersect(shell_carve_limiter_1)
181 .clear();
182
183 painter
184 .sphere_with_radius(
185 (center + (room_size / 2)).with_z(low_carve_base),
186 shell_carve_radius as f32,
187 )
188 .intersect(shell_carve_limiter_2)
189 .clear();
190 painter
192 .cylinder_with_radius(
193 center.with_z(shell_base + (3 * (shell_radius / 2))),
194 (shell_radius - 8) as f32,
195 shell_radius as f32,
196 )
197 .clear();
198
199 painter
200 .sphere_with_radius(
201 center.with_z(shell_base + (5 * (shell_radius / 2)) - 5),
202 (shell_radius - 8) as f32,
203 )
204 .clear();
205 let boss_pos = center.with_z(shell_base + (3 * (shell_radius / 2)));
206 painter.spawn(EntityInfo::at(boss_pos.as_()).with_asset_expect(
207 "common.entity.dungeon.sahagin.karkatha",
208 &mut thread_rng,
209 None,
210 ));
211 let var_towers = 32 + RandomField::new(0).get(center.with_z(base)) as i32 % 6;
213 let tower_positions = place_circular(center, (5 * (room_size / 2)) as f32, var_towers);
214
215 for tower_center_pos in tower_positions {
216 for dir in CARDINALS {
217 let tower_center = tower_center_pos + dir * 5;
218 let var_height =
219 RandomField::new(0).get(tower_center.with_z(base)) as i32 % (room_size / 2);
220 painter
221 .rounded_aabb(Aabb {
222 min: (tower_center - 10).with_z(base - room_size),
223 max: (tower_center + 10).with_z(base + (3 * (room_size / 2)) + var_height),
224 })
225 .fill(brick.clone());
226 }
227 }
228 let bldg_base = base + room_size;
229 let bldgs = var_towers / 3;
230 let beam_th = 2.5;
231 let bldg_positions = place_circular_as_vec(center, (5 * (room_size / 2)) as f32, bldgs);
232 for bldg_center in &bldg_positions {
234 let bldg_size = ((room_size / 4) + 1)
235 + RandomField::new(0).get(bldg_center.with_z(bldg_base)) as i32 % 3;
236 let points = 21;
237 let ring_0 = place_circular_as_vec(*bldg_center, (4 * (bldg_size / 2)) as f32, points);
238 let ring_1 = place_circular_as_vec(*bldg_center, (9 * (bldg_size / 2)) as f32, points);
239 let ring_2 = place_circular_as_vec(*bldg_center, (4 * (bldg_size / 2)) as f32, points);
240 let ring_3 = place_circular_as_vec(*bldg_center, (2 * (bldg_size / 2)) as f32, points);
241
242 let ring_4 = place_circular_as_vec(*bldg_center, (6 * (bldg_size / 2)) as f32, points);
243 let ring_5 = place_circular_as_vec(*bldg_center, (4 * (bldg_size / 2)) as f32, points);
244 let ring_6 = place_circular_as_vec(*bldg_center, (2 * (bldg_size / 2)) as f32, points);
245
246 for b in 0..=(ring_0.len() - 1) {
247 painter
248 .cubic_bezier(
249 ring_0[b].with_z(bldg_base + (3 * (bldg_size / 2))),
250 ring_1[b].with_z(bldg_base + (5 * (bldg_size / 2))),
251 ring_2[b].with_z(bldg_base + (10 * (bldg_size / 2))),
252 ring_3[b].with_z(bldg_base + (14 * (bldg_size / 2))),
253 beam_th,
254 )
255 .fill(jellyfish.clone());
256 if b == (ring_0.len() - 2) {
257 painter
258 .cubic_bezier(
259 ring_4[b].with_z(bldg_base + (12 * (bldg_size / 2))),
260 ring_5[b + 1].with_z(bldg_base + (14 * (bldg_size / 2))),
261 ring_6[0].with_z(bldg_base + (16 * (bldg_size / 2))),
262 bldg_center.with_z(bldg_base + (18 * (bldg_size / 2))),
263 beam_th,
264 )
265 .fill(jellyfish.clone());
266 } else if b == (ring_0.len() - 1) {
267 painter
268 .cubic_bezier(
269 ring_4[b].with_z(bldg_base + (12 * (bldg_size / 2))),
270 ring_5[0].with_z(bldg_base + (14 * (bldg_size / 2))),
271 ring_6[1].with_z(bldg_base + (16 * (bldg_size / 2))),
272 bldg_center.with_z(bldg_base + (18 * (bldg_size / 2))),
273 beam_th,
274 )
275 .fill(jellyfish.clone());
276 } else {
277 painter
278 .cubic_bezier(
279 ring_4[b].with_z(bldg_base + (12 * (bldg_size / 2))),
280 ring_5[b + 1].with_z(bldg_base + (14 * (bldg_size / 2))),
281 ring_6[b + 2].with_z(bldg_base + (16 * (bldg_size / 2))),
282 bldg_center.with_z(bldg_base + (18 * (bldg_size / 2))),
283 beam_th,
284 )
285 .fill(jellyfish.clone());
286 }
287 }
288 }
289 let key_chest_index_1 =
290 RandomField::new(0).get(center.with_z(base)) as usize % bldgs as usize;
291 for (p, bldg_center) in bldg_positions.iter().enumerate() {
292 let bldg_size = ((room_size / 4) + 1)
293 + RandomField::new(0).get(bldg_center.with_z(bldg_base)) as i32 % 3;
294
295 if p == (bldg_positions.len() - 1) {
298 painter
299 .line(
300 bldg_positions[p].with_z(bldg_base + (5 * (bldg_size / 2))),
301 bldg_positions[0].with_z(bldg_base + (5 * (bldg_size / 2))),
302 beam_th * 2.0,
303 )
304 .clear();
305 } else {
306 painter
307 .line(
308 bldg_positions[p].with_z(bldg_base + (5 * (bldg_size / 2))),
309 bldg_positions[p + 1].with_z(bldg_base + (5 * (bldg_size / 2))),
310 beam_th * 2.0,
311 )
312 .clear();
313 }
314 painter
316 .cylinder(Aabb {
317 min: (bldg_center - (2 * bldg_size) - 2).with_z(base),
318 max: (bldg_center + (2 * bldg_size) + 2)
319 .with_z(bldg_base + (5 * (bldg_size / 2)) - 4),
320 })
321 .fill(brick.clone());
322 let chest_pos = bldg_center - 4;
323 if p == key_chest_index_1 {
324 painter.sprite(
325 chest_pos.with_z(bldg_base + (9 * (bldg_size / 2))),
326 SpriteKind::SahaginChest,
327 );
328 }
329 painter
330 .cylinder(Aabb {
331 min: (chest_pos - 2).with_z(bldg_base + (9 * (bldg_size / 2)) - 1),
332 max: (chest_pos + 3).with_z(bldg_base + (9 * (bldg_size / 2))),
333 })
334 .fill(wood.clone());
335
336 random_npcs.push(chest_pos.with_z(bldg_base + (9 * (bldg_size / 2)) + 1));
337 }
338 for bldg_center in bldg_positions {
339 let bldg_size = ((room_size / 4) + 1)
340 + RandomField::new(0).get(bldg_center.with_z(bldg_base)) as i32 % 3;
341
342 painter
344 .cylinder(Aabb {
345 min: (bldg_center - 3).with_z(bldg_base),
346 max: (bldg_center + 3).with_z(bldg_base + (20 * (bldg_size / 2))),
347 })
348 .fill(wood.clone());
349 painter
350 .cone(Aabb {
351 min: (bldg_center - 4).with_z(bldg_base + (20 * (bldg_size / 2))),
352 max: (bldg_center + 4).with_z(bldg_base + (30 * (bldg_size / 2))),
353 })
354 .fill(wood.clone());
355 }
356
357 for room_center in rooms {
360 painter
361 .rounded_aabb(Aabb {
362 min: (room_center - room_size - (room_size / 2)).with_z(ground_floor),
363 max: (room_center + room_size + (room_size / 2)).with_z(base + 5),
364 })
365 .fill(brick.clone());
366 }
367 let key_chest_index_2 = RandomField::new(0).get(center.with_z(base)) as usize % rooms.len();
368 for (r, room_center) in rooms.iter().enumerate() {
369 painter
370 .rounded_aabb(Aabb {
371 min: (room_center - room_size).with_z(ground_floor + 1),
372 max: (room_center + room_size).with_z(base - 2),
373 })
374 .clear();
375 let cells = place_circular_as_vec(*room_center, room_size as f32, room_size / 2);
376 let spawns = place_circular_as_vec(*room_center, (room_size + 2) as f32, room_size / 2);
377 let cell_floors = (room_size / 6) - 1;
378 for f in 0..cell_floors {
379 let cell_floor = ground_floor + (room_size / 2) + ((cell_size_raw * 2) * f);
380 for cell_pos in &cells {
381 let cell_var = RandomField::new(0).get(cell_pos.with_z(cell_floor)) as i32 % 2;
382 let cell_size = cell_size_raw + cell_var;
383 painter
384 .rounded_aabb(Aabb {
385 min: (cell_pos - cell_size).with_z(cell_floor - cell_size),
386 max: (cell_pos + cell_size).with_z(cell_floor + cell_size),
387 })
388 .clear();
389 }
390 for spawn_pos in &spawns {
391 painter
392 .cylinder(Aabb {
393 min: (spawn_pos - 3).with_z(cell_floor - cell_size_raw - 1),
394 max: (spawn_pos + 4).with_z(cell_floor - cell_size_raw),
395 })
396 .fill(brick.clone());
397 painter
398 .cylinder(Aabb {
399 min: (spawn_pos - 2).with_z(cell_floor - cell_size_raw),
400 max: (spawn_pos + 3).with_z(cell_floor - cell_size_raw + 1),
401 })
402 .fill(brick.clone());
403 painter
404 .cylinder(Aabb {
405 min: (spawn_pos - 1).with_z(cell_floor - cell_size_raw + 1),
406 max: (spawn_pos + 2).with_z(cell_floor - cell_size_raw + 2),
407 })
408 .fill(brick.clone());
409 painter.sprite(
410 spawn_pos.with_z(cell_floor - cell_size_raw + 2),
411 match (RandomField::new(0)
412 .get(spawn_pos.with_z(cell_floor - cell_size_raw)))
413 % 75
414 {
415 0 => SpriteKind::DungeonChest2,
416 _ => SpriteKind::FireBowlGround,
417 },
418 );
419
420 let npc_pos = spawn_pos.with_z(cell_floor - cell_size_raw + 3);
421 if RandomField::new(0).get(npc_pos) as i32 % 5 == 1 {
422 random_npcs.push(npc_pos);
423 }
424 }
425 }
426 painter
428 .aabb(Aabb {
429 min: (room_center - room_size).with_z(ground_floor),
430 max: (room_center + room_size).with_z(ground_floor + (room_size / 3)),
431 })
432 .fill(brick.clone());
433
434 for m in 0..2 {
435 let mini_boss_pos = room_center.with_z(ground_floor + (room_size / 3));
436 painter.spawn(
437 EntityInfo::at((mini_boss_pos + (1 * m)).as_()).with_asset_expect(
438 "common.entity.dungeon.sahagin.hakulaq",
439 &mut thread_rng,
440 None,
441 ),
442 );
443 }
444 for c in 0..5 {
445 let crab_pos = room_center.with_z(ground_floor + (room_size / 3));
446 painter.spawn(
447 EntityInfo::at((crab_pos - (1 * c)).as_()).with_asset_expect(
448 "common.entity.dungeon.sahagin.soldier_crab",
449 &mut thread_rng,
450 None,
451 ),
452 );
453 }
454 if r == key_chest_index_2 {
455 painter.sprite(
456 (room_center - 1).with_z(ground_floor + (room_size / 3)),
457 SpriteKind::SahaginChest,
458 );
459 }
460
461 let center_entry = RandomField::new(0).get(center.with_z(base)) % 4;
462
463 if r > 0 {
464 let rooms_center =
466 place_circular(center, (outer_room_radius - 15) as f32, room_size / 2);
467 let room_base = base - (room_size / 2) + (room_size / 2);
468 for room_center in rooms_center {
469 let room_var =
470 RandomField::new(0).get(room_center.with_z(room_base)) as i32 % 10;
471 let room_var_size = room_size - room_var;
472 painter
473 .rounded_aabb(Aabb {
474 min: (room_center - room_var_size).with_z(room_base),
475 max: (room_center + room_var_size).with_z(room_base + room_var_size),
476 })
477 .fill(brick.clone());
478 }
479 if r == (center_entry + 1) as usize {
480 painter
481 .line(
482 room_center.with_z(ground_floor + room_size),
483 center.with_z(ground_floor + room_size),
484 15.0,
485 )
486 .clear();
487 }
488 }
489 }
490 for p in 0..tunnel_points.len() {
492 if p == tunnel_points.len() - 1 {
493 painter
494 .line(
495 tunnel_points[p].with_z(ground_floor + (room_size / 2)),
496 tunnel_points[0].with_z(ground_floor + (room_size / 2)),
497 5.0,
498 )
499 .clear();
500 } else {
501 painter
502 .line(
503 tunnel_points[p].with_z(ground_floor + (room_size / 2)),
504 tunnel_points[p + 1].with_z(ground_floor + (room_size / 2)),
505 5.0,
506 )
507 .clear();
508 }
509 }
510 painter
512 .rounded_aabb(Aabb {
513 min: (center - room_size - 10).with_z(base - 2),
514 max: (center + room_size + 10).with_z(base + room_size),
515 })
516 .fill(brick.clone());
517 let clear_limiter = painter.aabb(Aabb {
518 min: (center - room_size - 8).with_z(base + (room_size / 5)),
519 max: (center + room_size + 8).with_z(base + room_size - 1),
520 });
521 painter
522 .rounded_aabb(Aabb {
523 min: (center - room_size - 8).with_z(base),
524 max: (center + room_size + 8).with_z(base + room_size - 1),
525 })
526 .intersect(clear_limiter)
527 .clear();
528
529 let var_lamps = 25 + RandomField::new(0).get(center.with_z(base)) as i32 % 5;
531 let lamp_positions = place_circular(center, (room_size + 5) as f32, var_lamps);
532
533 for lamp_pos in lamp_positions {
534 painter.sprite(
535 lamp_pos.with_z(base + (room_size / 5)),
536 SpriteKind::FireBowlGround,
537 );
538 }
539
540 let stair_radius = room_size / 3;
542 for e in 0..=1 {
543 let stairs_pos = center - (room_size / 2) + ((room_size * 2) * e);
544 if e > 0 {
546 painter
547 .rounded_aabb(Aabb {
548 min: (stairs_pos - stair_radius - 5).with_z(base - (room_size / 2)),
549 max: (stairs_pos + stair_radius + 5)
550 .with_z(base + (room_size / 5) + (3 * (room_size / 2))),
551 })
552 .fill(brick.clone());
553 painter
555 .aabb(Aabb {
556 min: Vec2::new(stairs_pos.x - stair_radius - 8, stairs_pos.y - 2)
557 .with_z(base + (room_size / 5) + (2 * (room_size / 2))),
558 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
559 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
560 })
561 .clear();
562 painter
563 .aabb(Aabb {
564 min: Vec2::new(stairs_pos.x - stair_radius - 8, stairs_pos.y - 1)
565 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
566 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
567 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 8),
568 })
569 .clear();
570 painter
572 .aabb(Aabb {
573 min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y - 2)
574 .with_z(base + (room_size / 5) + (2 * (room_size / 2))),
575 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
576 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
577 })
578 .fill(key_door.clone());
579 painter
580 .aabb(Aabb {
581 min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y - 1)
582 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
583 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
584 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 8),
585 })
586 .fill(key_door.clone());
587 painter
588 .aabb(Aabb {
589 min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y)
590 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 2),
591 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
592 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 3),
593 })
594 .fill(key_hole.clone());
595 for s in 0..4 {
597 painter
598 .aabb(Aabb {
599 min: Vec2::new(stairs_pos.x - stair_radius - 2 - s, stairs_pos.y - 2)
600 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) - 1 - s),
601 max: Vec2::new(stairs_pos.x - stair_radius - 1 - s, stairs_pos.y + 2)
602 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
603 })
604 .clear();
605 }
606 } else {
607 painter
610 .cylinder(Aabb {
611 min: (stairs_pos - stair_radius - 4).with_z(base + (room_size / 5)),
612 max: (stairs_pos + stair_radius + 4).with_z(base + room_size - 2),
613 })
614 .fill(brick.clone());
615 painter
616 .cylinder(Aabb {
617 min: (stairs_pos - stair_radius).with_z(base + (room_size / 5)),
618 max: (stairs_pos + stair_radius).with_z(base + room_size - 3),
619 })
620 .clear();
621 painter
623 .aabb(Aabb {
624 min: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y - 2)
625 .with_z(base + (room_size / 5) + 2),
626 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
627 .with_z(base + (room_size / 5) + 9),
628 })
629 .clear();
630 painter
631 .aabb(Aabb {
632 min: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y - 1)
633 .with_z(base + (room_size / 5) + 9),
634 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
635 .with_z(base + (room_size / 5) + 10),
636 })
637 .clear();
638 painter
640 .aabb(Aabb {
641 min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y - 2)
642 .with_z(base + (room_size / 5) + 2),
643 max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 2)
644 .with_z(base + (room_size / 5) + 9),
645 })
646 .fill(key_door.clone());
647 painter
648 .aabb(Aabb {
649 min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y - 1)
650 .with_z(base + (room_size / 5) + 9),
651 max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 1)
652 .with_z(base + (room_size / 5) + 10),
653 })
654 .fill(key_door.clone());
655 painter
656 .aabb(Aabb {
657 min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y)
658 .with_z(base + (room_size / 5) + 3),
659 max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 1)
660 .with_z(base + (room_size / 5) + 4),
661 })
662 .fill(key_hole.clone());
663 }
664
665 let stairs_clear = painter.cylinder(Aabb {
666 min: (stairs_pos - stair_radius).with_z(ground_floor + (room_size / 3)),
667 max: (stairs_pos + stair_radius)
668 .with_z(base + (room_size / 5) + (((3 * (room_size / 2)) - 6) * e)),
669 });
670 stairs_clear.clear();
671 stairs_clear
672 .sample(spiral_staircase(
673 stairs_pos.with_z(ground_floor + (room_size / 3)),
674 (stair_radius + 1) as f32,
675 2.5,
676 (room_size - 5) as f32,
677 ))
678 .fill(wood.clone());
679 }
680
681 let boss_entry_pos = center + (room_size / 3);
683 let rope_pos = center + (room_size / 3) - 2;
684 let spike_pos = center + (room_size / 3) - 1;
685
686 painter
687 .cylinder(Aabb {
688 min: (boss_entry_pos - stair_radius).with_z(base + room_size - 5),
689 max: (boss_entry_pos + stair_radius).with_z(base + (room_size * 2) - 10),
690 })
691 .fill(wood.clone());
692 painter
693 .cylinder(Aabb {
694 min: (boss_entry_pos - 3).with_z(base + (room_size * 2) - 10),
695 max: (boss_entry_pos + 4).with_z(base + (room_size * 2) - 7),
696 })
697 .fill(wood.clone());
698 painter
699 .cylinder(Aabb {
700 min: (boss_entry_pos - 2).with_z(base + room_size - 5),
701 max: (boss_entry_pos + 3).with_z(base + (room_size * 2) - 7),
702 })
703 .clear();
704
705 painter
706 .aabb(Aabb {
707 min: rope_pos.with_z(base + (room_size / 4) + 1),
708 max: (rope_pos + 1).with_z(base + room_size - 5),
709 })
710 .fill(rope.clone());
711
712 painter
713 .cylinder(Aabb {
714 min: (spike_pos - 3).with_z(base + (room_size * 2) - 7),
715 max: (spike_pos + 4).with_z(base + (room_size * 2) - 5),
716 })
717 .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
718 let npc_pos = boss_entry_pos;
720 painter.spawn(
721 EntityInfo::at((npc_pos.with_z(base + (room_size / 4))).as_()).with_asset_expect(
722 "common.entity.dungeon.sahagin.tidalwarrior",
723 &mut thread_rng,
724 None,
725 ),
726 );
727 painter.spawn(
728 EntityInfo::at(((npc_pos - 2).with_z(base + (room_size / 4))).as_()).with_asset_expect(
729 "common.entity.dungeon.sahagin.hakulaq",
730 &mut thread_rng,
731 None,
732 ),
733 );
734 for c in 0..5 {
735 let crab_pos = (npc_pos + (1 * c)).with_z(base + (room_size / 4));
736 painter.spawn(EntityInfo::at(crab_pos.as_()).with_asset_expect(
737 "common.entity.dungeon.sahagin.soldier_crab",
738 &mut thread_rng,
739 None,
740 ));
741 }
742
743 for m in 0..2 {
745 let mini_boss_pos = center.with_z(base + room_size + 5);
746 painter.spawn(
747 EntityInfo::at((mini_boss_pos + (1 * m)).as_()).with_asset_expect(
748 "common.entity.dungeon.sahagin.hakulaq",
749 &mut thread_rng,
750 None,
751 ),
752 );
753 }
754
755 for c in 0..5 {
756 let crab_pos = center.with_z(base + room_size + 5);
757 painter.spawn(
758 EntityInfo::at((crab_pos - (1 * c)).as_()).with_asset_expect(
759 "common.entity.dungeon.sahagin.soldier_crab",
760 &mut thread_rng,
761 None,
762 ),
763 );
764 }
765
766 for pos in random_npcs {
767 let entities = [
768 "common.entity.dungeon.sahagin.sniper",
769 "common.entity.dungeon.sahagin.sniper",
770 "common.entity.dungeon.sahagin.sniper",
771 "common.entity.dungeon.sahagin.sorcerer",
772 "common.entity.dungeon.sahagin.spearman",
773 ];
774 let npc = entities[(RandomField::new(0).get(pos) % entities.len() as u32) as usize];
775 painter.spawn(EntityInfo::at(pos.as_()).with_asset_expect(npc, &mut thread_rng, None));
776 }
777 }
778}