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