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", unsafe(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 - c).with_z(ground_floor + (room_size / 3));
446 painter.spawn(EntityInfo::at(crab_pos.as_()).with_asset_expect(
447 "common.entity.dungeon.sahagin.soldier_crab",
448 &mut thread_rng,
449 None,
450 ));
451 }
452 if r == key_chest_index_2 {
453 painter.sprite(
454 (room_center - 1).with_z(ground_floor + (room_size / 3)),
455 SpriteKind::SahaginChest,
456 );
457 }
458
459 let center_entry = RandomField::new(0).get(center.with_z(base)) % 4;
460
461 if r > 0 {
462 let rooms_center =
464 place_circular(center, (outer_room_radius - 15) as f32, room_size / 2);
465 let room_base = base - (room_size / 2) + (room_size / 2);
466 for room_center in rooms_center {
467 let room_var =
468 RandomField::new(0).get(room_center.with_z(room_base)) as i32 % 10;
469 let room_var_size = room_size - room_var;
470 painter
471 .rounded_aabb(Aabb {
472 min: (room_center - room_var_size).with_z(room_base),
473 max: (room_center + room_var_size).with_z(room_base + room_var_size),
474 })
475 .fill(brick.clone());
476 }
477 if r == (center_entry + 1) as usize {
478 painter
479 .line(
480 room_center.with_z(ground_floor + room_size),
481 center.with_z(ground_floor + room_size),
482 15.0,
483 )
484 .clear();
485 }
486 }
487 }
488 for p in 0..tunnel_points.len() {
490 if p == tunnel_points.len() - 1 {
491 painter
492 .line(
493 tunnel_points[p].with_z(ground_floor + (room_size / 2)),
494 tunnel_points[0].with_z(ground_floor + (room_size / 2)),
495 5.0,
496 )
497 .clear();
498 } else {
499 painter
500 .line(
501 tunnel_points[p].with_z(ground_floor + (room_size / 2)),
502 tunnel_points[p + 1].with_z(ground_floor + (room_size / 2)),
503 5.0,
504 )
505 .clear();
506 }
507 }
508 painter
510 .rounded_aabb(Aabb {
511 min: (center - room_size - 10).with_z(base - 2),
512 max: (center + room_size + 10).with_z(base + room_size),
513 })
514 .fill(brick.clone());
515 let clear_limiter = painter.aabb(Aabb {
516 min: (center - room_size - 8).with_z(base + (room_size / 5)),
517 max: (center + room_size + 8).with_z(base + room_size - 1),
518 });
519 painter
520 .rounded_aabb(Aabb {
521 min: (center - room_size - 8).with_z(base),
522 max: (center + room_size + 8).with_z(base + room_size - 1),
523 })
524 .intersect(clear_limiter)
525 .clear();
526
527 let var_lamps = 25 + RandomField::new(0).get(center.with_z(base)) as i32 % 5;
529 let lamp_positions = place_circular(center, (room_size + 5) as f32, var_lamps);
530
531 for lamp_pos in lamp_positions {
532 painter.sprite(
533 lamp_pos.with_z(base + (room_size / 5)),
534 SpriteKind::FireBowlGround,
535 );
536 }
537
538 let stair_radius = room_size / 3;
540 for e in 0..=1 {
541 let stairs_pos = center - (room_size / 2) + ((room_size * 2) * e);
542 if e > 0 {
544 painter
545 .rounded_aabb(Aabb {
546 min: (stairs_pos - stair_radius - 5).with_z(base - (room_size / 2)),
547 max: (stairs_pos + stair_radius + 5)
548 .with_z(base + (room_size / 5) + (3 * (room_size / 2))),
549 })
550 .fill(brick.clone());
551 painter
553 .aabb(Aabb {
554 min: Vec2::new(stairs_pos.x - stair_radius - 8, stairs_pos.y - 2)
555 .with_z(base + (room_size / 5) + (2 * (room_size / 2))),
556 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
557 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
558 })
559 .clear();
560 painter
561 .aabb(Aabb {
562 min: Vec2::new(stairs_pos.x - stair_radius - 8, stairs_pos.y - 1)
563 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
564 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
565 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 8),
566 })
567 .clear();
568 painter
570 .aabb(Aabb {
571 min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y - 2)
572 .with_z(base + (room_size / 5) + (2 * (room_size / 2))),
573 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
574 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
575 })
576 .fill(key_door.clone());
577 painter
578 .aabb(Aabb {
579 min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y - 1)
580 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
581 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
582 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 8),
583 })
584 .fill(key_door.clone());
585 painter
586 .aabb(Aabb {
587 min: Vec2::new(stairs_pos.x - stair_radius - 1, stairs_pos.y)
588 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 2),
589 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
590 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 3),
591 })
592 .fill(key_hole.clone());
593 for s in 0..4 {
595 painter
596 .aabb(Aabb {
597 min: Vec2::new(stairs_pos.x - stair_radius - 2 - s, stairs_pos.y - 2)
598 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) - 1 - s),
599 max: Vec2::new(stairs_pos.x - stair_radius - 1 - s, stairs_pos.y + 2)
600 .with_z(base + (room_size / 5) + (2 * (room_size / 2)) + 7),
601 })
602 .clear();
603 }
604 } else {
605 painter
608 .cylinder(Aabb {
609 min: (stairs_pos - stair_radius - 4).with_z(base + (room_size / 5)),
610 max: (stairs_pos + stair_radius + 4).with_z(base + room_size - 2),
611 })
612 .fill(brick.clone());
613 painter
614 .cylinder(Aabb {
615 min: (stairs_pos - stair_radius).with_z(base + (room_size / 5)),
616 max: (stairs_pos + stair_radius).with_z(base + room_size - 3),
617 })
618 .clear();
619 painter
621 .aabb(Aabb {
622 min: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y - 2)
623 .with_z(base + (room_size / 5) + 2),
624 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 2)
625 .with_z(base + (room_size / 5) + 9),
626 })
627 .clear();
628 painter
629 .aabb(Aabb {
630 min: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y - 1)
631 .with_z(base + (room_size / 5) + 9),
632 max: Vec2::new(stairs_pos.x - stair_radius, stairs_pos.y + 1)
633 .with_z(base + (room_size / 5) + 10),
634 })
635 .clear();
636 painter
638 .aabb(Aabb {
639 min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y - 2)
640 .with_z(base + (room_size / 5) + 2),
641 max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 2)
642 .with_z(base + (room_size / 5) + 9),
643 })
644 .fill(key_door.clone());
645 painter
646 .aabb(Aabb {
647 min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y - 1)
648 .with_z(base + (room_size / 5) + 9),
649 max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 1)
650 .with_z(base + (room_size / 5) + 10),
651 })
652 .fill(key_door.clone());
653 painter
654 .aabb(Aabb {
655 min: Vec2::new(stairs_pos.x - stair_radius - 4, stairs_pos.y)
656 .with_z(base + (room_size / 5) + 3),
657 max: Vec2::new(stairs_pos.x - stair_radius - 3, stairs_pos.y + 1)
658 .with_z(base + (room_size / 5) + 4),
659 })
660 .fill(key_hole.clone());
661 }
662
663 let stairs_clear = painter.cylinder(Aabb {
664 min: (stairs_pos - stair_radius).with_z(ground_floor + (room_size / 3)),
665 max: (stairs_pos + stair_radius)
666 .with_z(base + (room_size / 5) + (((3 * (room_size / 2)) - 6) * e)),
667 });
668 stairs_clear.clear();
669 stairs_clear
670 .sample(spiral_staircase(
671 stairs_pos.with_z(ground_floor + (room_size / 3)),
672 (stair_radius + 1) as f32,
673 2.5,
674 (room_size - 5) as f32,
675 ))
676 .fill(wood.clone());
677 }
678
679 let boss_entry_pos = center + (room_size / 3);
681 let rope_pos = center + (room_size / 3) - 2;
682 let spike_pos = center + (room_size / 3) - 1;
683
684 painter
685 .cylinder(Aabb {
686 min: (boss_entry_pos - stair_radius).with_z(base + room_size - 5),
687 max: (boss_entry_pos + stair_radius).with_z(base + (room_size * 2) - 10),
688 })
689 .fill(wood.clone());
690 painter
691 .cylinder(Aabb {
692 min: (boss_entry_pos - 3).with_z(base + (room_size * 2) - 10),
693 max: (boss_entry_pos + 4).with_z(base + (room_size * 2) - 7),
694 })
695 .fill(wood.clone());
696 painter
697 .cylinder(Aabb {
698 min: (boss_entry_pos - 2).with_z(base + room_size - 5),
699 max: (boss_entry_pos + 3).with_z(base + (room_size * 2) - 7),
700 })
701 .clear();
702
703 painter
704 .aabb(Aabb {
705 min: rope_pos.with_z(base + (room_size / 4) + 1),
706 max: (rope_pos + 1).with_z(base + room_size - 5),
707 })
708 .fill(rope.clone());
709
710 painter
711 .cylinder(Aabb {
712 min: (spike_pos - 3).with_z(base + (room_size * 2) - 7),
713 max: (spike_pos + 4).with_z(base + (room_size * 2) - 5),
714 })
715 .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
716 let npc_pos = boss_entry_pos;
718 painter.spawn(
719 EntityInfo::at((npc_pos.with_z(base + (room_size / 4))).as_()).with_asset_expect(
720 "common.entity.dungeon.sahagin.tidalwarrior",
721 &mut thread_rng,
722 None,
723 ),
724 );
725 painter.spawn(
726 EntityInfo::at(((npc_pos - 2).with_z(base + (room_size / 4))).as_()).with_asset_expect(
727 "common.entity.dungeon.sahagin.hakulaq",
728 &mut thread_rng,
729 None,
730 ),
731 );
732 for c in 0..5 {
733 let crab_pos = (npc_pos + c).with_z(base + (room_size / 4));
734 painter.spawn(EntityInfo::at(crab_pos.as_()).with_asset_expect(
735 "common.entity.dungeon.sahagin.soldier_crab",
736 &mut thread_rng,
737 None,
738 ));
739 }
740
741 for m in 0..2 {
743 let mini_boss_pos = center.with_z(base + room_size + 5);
744 painter.spawn(
745 EntityInfo::at((mini_boss_pos + (1 * m)).as_()).with_asset_expect(
746 "common.entity.dungeon.sahagin.hakulaq",
747 &mut thread_rng,
748 None,
749 ),
750 );
751 }
752
753 for c in 0..5 {
754 let crab_pos = (center - c).with_z(base + room_size + 5);
755 painter.spawn(EntityInfo::at(crab_pos.as_()).with_asset_expect(
756 "common.entity.dungeon.sahagin.soldier_crab",
757 &mut thread_rng,
758 None,
759 ));
760 }
761
762 for pos in random_npcs {
763 let entities = [
764 "common.entity.dungeon.sahagin.sniper",
765 "common.entity.dungeon.sahagin.sniper",
766 "common.entity.dungeon.sahagin.sniper",
767 "common.entity.dungeon.sahagin.sorcerer",
768 "common.entity.dungeon.sahagin.spearman",
769 ];
770 let npc = entities[(RandomField::new(0).get(pos) % entities.len() as u32) as usize];
771 painter.spawn(EntityInfo::at(pos.as_()).with_asset_expect(npc, &mut thread_rng, None));
772 }
773 }
774}