1use super::*;
2use crate::{
3 IndexRef, Land,
4 assets::AssetHandle,
5 site2::{gen::PrimitiveTransform, util::Dir},
6 util::{FastNoise, NEIGHBORS, NEIGHBORS3, RandomField, attempt, sampler::Sampler},
7};
8use common::{
9 generation::{ChunkSupplement, EntityInfo},
10 terrain::{Structure as PrefabStructure, StructuresGroup},
11};
12use lazy_static::lazy_static;
13use rand::prelude::*;
14use std::{
15 f32::consts::{PI, TAU},
16 sync::Arc,
17};
18use vek::*;
19
20pub struct AdletStronghold {
21 name: String,
22 entrance: Vec2<i32>,
23 surface_radius: i32,
24 outer_structures: Vec<(AdletStructure, Vec2<i32>, Dir)>,
27 tunnel_length: i32,
28 cavern_center: Vec2<i32>,
29 cavern_alt: f32,
30 cavern_radius: i32,
31 cavern_structures: Vec<(AdletStructure, Vec2<i32>, Dir)>,
34}
35
36#[derive(Copy, Clone)]
37enum AdletStructure {
38 Igloo,
39 TunnelEntrance,
40 SpeleothemCluster,
41 Bonfire,
42 YetiPit,
43 Tannery,
44 AnimalPen,
45 CookFire,
46 RockHut,
47 BoneHut,
48 BossBoneHut,
49}
50
51impl AdletStructure {
52 fn required_separation(&self, other: &Self) -> i32 {
53 let radius = |structure: &Self| match structure {
54 Self::Igloo => 20,
55 Self::TunnelEntrance => 32,
56 Self::SpeleothemCluster => 4,
57 Self::YetiPit => 32,
58 Self::Tannery => 6,
59 Self::AnimalPen => 8,
60 Self::CookFire => 3,
61 Self::RockHut => 4,
62 Self::BoneHut => 11,
63 Self::BossBoneHut => 14,
64 Self::Bonfire => 10,
65 };
66
67 let additional_padding = match (self, other) {
68 (Self::Igloo, Self::Igloo) => 3,
69 (Self::BoneHut, Self::BoneHut) => 3,
70 (Self::Tannery, Self::Tannery) => 5,
71 (Self::CookFire, Self::CookFire) => 20,
72 (Self::AnimalPen, Self::AnimalPen) => 8,
73 (Self::SpeleothemCluster, Self::SpeleothemCluster) => 0,
75 (Self::SpeleothemCluster, _) | (_, Self::SpeleothemCluster) => 5,
76 _ => 0,
77 };
78
79 radius(self) + radius(other) + additional_padding
80 }
81}
82
83impl AdletStronghold {
84 pub fn generate(wpos: Vec2<i32>, land: &Land, rng: &mut impl Rng, _index: IndexRef) -> Self {
85 let name = NameGen::location(rng).generate_adlet();
86 let entrance = wpos;
87
88 let surface_radius: i32 = {
89 let unit_size = rng.gen_range(10..12);
90 let num_units = rng.gen_range(4..8);
91 let variation = rng.gen_range(20..30);
92 unit_size * num_units + variation
93 };
94
95 let angle_samples = (0..64).map(|x| x as f32 / 64.0 * TAU);
97 let angle = angle_samples
100 .max_by_key(|theta| {
101 let entrance_height = land.get_alt_approx(entrance);
102 let height =
103 |pos: Vec2<f32>| land.get_alt_approx(pos.as_() + entrance) - entrance_height;
104 let (x, y) = (theta.cos(), theta.sin());
105 (40..=50)
106 .map(|r| {
107 let rpos = Vec2::new(r as f32 * x, r as f32 * y);
108 height(rpos) as i32
109 })
110 .sum::<i32>()
111 })
112 .unwrap_or(0.0);
113
114 let cavern_radius: i32 = {
115 let unit_size = rng.gen_range(10..15);
116 let num_units = rng.gen_range(5..8);
117 let variation = rng.gen_range(20..40);
118 unit_size * num_units + variation
119 };
120
121 let tunnel_length = rng.gen_range(35_i32..50);
122
123 let cavern_center = entrance
124 + (Vec2::new(angle.cos(), angle.sin()) * (tunnel_length as f32 + cavern_radius as f32))
125 .as_();
126
127 let cavern_alt = (land.get_alt_approx(cavern_center) - cavern_radius as f32)
128 .min(land.get_alt_approx(entrance));
129
130 let mut outer_structures = Vec::<(AdletStructure, Vec2<i32>, Dir)>::new();
131
132 let entrance_dir = Dir::from_vec2(entrance - cavern_center);
133 outer_structures.push((AdletStructure::TunnelEntrance, Vec2::zero(), entrance_dir));
134
135 let desired_structures = surface_radius.pow(2) / 100;
136 for _ in 0..desired_structures {
137 if let Some((rpos, kind)) = attempt(50, || {
138 let structure_kind = AdletStructure::Igloo;
139 let structure_center = {
148 let theta = rng.gen::<f32>() * TAU;
149 let radius = surface_radius as f32 * rng.gen::<f32>().sqrt() * 0.8;
151 let x = radius * theta.sin();
152 let y = radius * theta.cos();
153 Vec2::new(x, y).as_()
154 };
155
156 let tunnel_line = LineSegment2 {
157 start: entrance,
158 end: entrance - entrance_dir.to_vec2() * 100,
159 };
160
161 if land
163 .get_chunk_wpos(structure_center.as_() + entrance)
164 .is_some_and(|c| c.is_underwater())
165 || outer_structures.iter().any(|(kind, rpos, _dir)| {
166 structure_center.distance_squared(*rpos)
167 < structure_kind.required_separation(kind).pow(2)
168 })
169 || tunnel_line
170 .as_::<f32>()
171 .distance_to_point((structure_center + entrance).as_::<f32>())
172 < 25.0
173 {
174 None
175 } else {
176 Some((structure_center, structure_kind))
177 }
178 }) {
179 let dir_to_wall = Dir::from_vec2(rpos);
180 let door_rng: u32 = rng.gen_range(0..9);
181 let door_dir = match door_rng {
182 0..=3 => dir_to_wall,
183 4..=5 => dir_to_wall.rotated_cw(),
184 6..=7 => dir_to_wall.rotated_ccw(),
185 _ => dir_to_wall.opposite(),
187 };
188 outer_structures.push((kind, rpos, door_dir));
189 }
190 }
191
192 let mut cavern_structures = Vec::<(AdletStructure, Vec2<i32>, Dir)>::new();
193
194 fn valid_cavern_struct_pos(
195 structures: &[(AdletStructure, Vec2<i32>, Dir)],
196 structure: AdletStructure,
197 rpos: Vec2<i32>,
198 ) -> bool {
199 structures.iter().all(|(kind, rpos2, _dir)| {
200 rpos.distance_squared(*rpos2) > structure.required_separation(kind).pow(2)
201 })
202 }
203
204 let desired_speleothem_clusters = cavern_radius.pow(2) / 2500;
206 for _ in 0..desired_speleothem_clusters {
207 if let Some(mut rpos) = attempt(25, || {
208 let rpos = {
209 let theta = rng.gen_range(0.0..TAU);
210 let radius = rng.gen::<f32>().sqrt() * cavern_radius as f32;
212 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
213 };
214 valid_cavern_struct_pos(&cavern_structures, AdletStructure::SpeleothemCluster, rpos)
215 .then_some(rpos)
216 }) {
217 cavern_structures.push((AdletStructure::SpeleothemCluster, rpos, Dir::X));
219 let desired_adjacent_clusters = rng.gen_range(1..5);
220 for _ in 0..desired_adjacent_clusters {
221 let adj_rpos = {
223 let theta = rng.gen_range(0.0..TAU);
224 let radius = rng.gen_range(1.0..5.0);
225 let rrpos = Vec2::new(theta.cos() * radius, theta.sin() * radius).as_();
226 rpos + rrpos
227 };
228 if valid_cavern_struct_pos(
229 &cavern_structures,
230 AdletStructure::SpeleothemCluster,
231 adj_rpos,
232 ) {
233 cavern_structures.push((
234 AdletStructure::SpeleothemCluster,
235 adj_rpos,
236 Dir::X,
237 ));
238 rpos = adj_rpos;
240 } else {
241 break;
244 }
245 }
246 }
247 }
248
249 if let Some(rpos) = attempt(50, || {
251 let rpos = {
252 let theta = rng.gen_range(0.0..TAU);
253 let radius = rng.gen::<f32>() * cavern_radius as f32 * 0.5;
254 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
255 };
256 valid_cavern_struct_pos(&cavern_structures, AdletStructure::BossBoneHut, rpos)
257 .then_some(rpos)
258 })
259 .or_else(|| {
260 attempt(100, || {
261 let rpos = {
262 let theta = rng.gen_range(0.0..TAU);
263 let radius = rng.gen::<f32>().sqrt() * cavern_radius as f32;
266 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
267 };
268 valid_cavern_struct_pos(&cavern_structures, AdletStructure::BossBoneHut, rpos)
269 .then_some(rpos)
270 })
271 }) {
272 cavern_structures.push((AdletStructure::BossBoneHut, rpos, Dir::X));
274 }
275
276 if let Some(rpos) = attempt(50, || {
278 let rpos = {
279 let theta = rng.gen_range(0.0..TAU);
280 let radius = cavern_radius as f32;
281 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
282 };
283 valid_cavern_struct_pos(&cavern_structures, AdletStructure::YetiPit, rpos)
284 .then_some(rpos)
285 })
286 .or_else(|| {
287 attempt(100, || {
288 let rpos = {
289 let theta = rng.gen_range(0.0..TAU);
290 let radius = rng.gen::<f32>().sqrt() * cavern_radius as f32;
293 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
294 };
295 valid_cavern_struct_pos(&cavern_structures, AdletStructure::YetiPit, rpos)
296 .then_some(rpos)
297 })
298 }) {
299 cavern_structures.push((AdletStructure::YetiPit, rpos, Dir::X));
301 }
302
303 if let Some(rpos) = attempt(50, || {
305 let rpos = {
306 let theta = rng.gen_range(0.0..TAU);
307 let radius = rng.gen::<f32>().sqrt() * cavern_radius as f32 * 0.9;
308 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
309 };
310 valid_cavern_struct_pos(&cavern_structures, AdletStructure::Bonfire, rpos)
311 .then_some(rpos)
312 }) {
313 cavern_structures.push((AdletStructure::Bonfire, rpos, Dir::X));
315 }
316
317 let desired_rock_huts = cavern_radius / 5;
319 for _ in 0..desired_rock_huts {
320 if let Some(rpos) = attempt(25, || {
321 let rpos = {
322 let theta = rng.gen_range(0.0..TAU);
323 let radius = cavern_radius as f32 - 1.0;
324 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
325 };
326 valid_cavern_struct_pos(&cavern_structures, AdletStructure::RockHut, rpos)
327 .then_some(rpos)
328 }) {
329 cavern_structures.push((AdletStructure::RockHut, rpos, Dir::X));
331 }
332 }
333
334 let desired_structures = cavern_radius.pow(2) / 200;
336 for _ in 0..desired_structures {
337 if let Some((structure, rpos)) = attempt(45, || {
338 let rpos = {
339 let theta = rng.gen_range(0.0..TAU);
340 let radius = rng.gen::<f32>().sqrt() * cavern_radius as f32 * 0.9;
342 Vec2::new(theta.cos() * radius, theta.sin() * radius).as_()
343 };
344 let structure = match rng.gen_range(0..7) {
345 0..=2 => AdletStructure::BoneHut,
346 3..=4 => AdletStructure::CookFire,
347 5 => AdletStructure::Tannery,
348 _ => AdletStructure::AnimalPen,
349 };
350
351 valid_cavern_struct_pos(&cavern_structures, structure, rpos)
352 .then_some((structure, rpos))
353 }) {
354 let dir = Dir::from_vec2(rpos).opposite();
356 cavern_structures.push((structure, rpos, dir));
357 }
358 }
359
360 Self {
361 name,
362 entrance,
363 surface_radius,
364 outer_structures,
365 tunnel_length,
366 cavern_center,
367 cavern_radius,
368 cavern_alt,
369 cavern_structures,
370 }
371 }
372
373 pub fn name(&self) -> &str { &self.name }
374
375 pub fn radius(&self) -> i32 { self.cavern_radius + self.tunnel_length + 5 }
378
379 pub fn plot_tiles(&self, origin: Vec2<i32>) -> (Aabr<i32>, Aabr<i32>) {
380 let size = self.cavern_radius / tile::TILE_SIZE as i32;
382 let offset = (self.cavern_center - origin) / tile::TILE_SIZE as i32;
383 let cavern_aabr = Aabr {
384 min: Vec2::broadcast(-size) + offset,
385 max: Vec2::broadcast(size) + offset,
386 };
387 let size = (self.surface_radius * 5 / 4) / tile::TILE_SIZE as i32;
389 let offset = (self.entrance - origin) / tile::TILE_SIZE as i32;
390 let surface_aabr = Aabr {
391 min: Vec2::broadcast(-size) + offset,
392 max: Vec2::broadcast(size) + offset,
393 };
394 (cavern_aabr, surface_aabr)
395 }
396
397 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
398 SpawnRules {
399 waypoints: false,
400 trees: wpos.distance_squared(self.entrance) > (self.surface_radius * 5 / 4).pow(2),
401 ..SpawnRules::default()
402 }
403 }
404
405 pub fn apply_supplement(
407 &self,
408 _dynamic_rng: &mut impl Rng,
410 wpos2d: Vec2<i32>,
411 _supplement: &mut ChunkSupplement,
412 ) {
413 let rpos = wpos2d - self.cavern_center;
414 let _area = Aabr {
415 min: rpos,
416 max: rpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
417 };
418 }
419}
420
421impl Structure for AdletStronghold {
422 #[cfg(feature = "use-dyn-lib")]
423 const UPDATE_FN: &'static [u8] = b"render_adletstronghold\0";
424
425 #[cfg_attr(feature = "be-dyn-lib", export_name = "render_adletstronghold")]
426 fn render_inner(&self, _site: &Site, land: &Land, painter: &Painter) {
427 let snow_ice_fill = Fill::Sampling(Arc::new(|wpos| {
428 Some(match (RandomField::new(0).get(wpos)) % 250 {
429 0..=2 => Block::new(BlockKind::Ice, Rgb::new(120, 160, 255)),
430 3..=10 => Block::new(BlockKind::ArtSnow, Rgb::new(138, 147, 217)),
431 11..=20 => Block::new(BlockKind::ArtSnow, Rgb::new(213, 213, 242)),
432 21..=35 => Block::new(BlockKind::ArtSnow, Rgb::new(231, 230, 247)),
433 36..=62 => Block::new(BlockKind::ArtSnow, Rgb::new(180, 181, 227)),
434 _ => Block::new(BlockKind::ArtSnow, Rgb::new(209, 212, 238)),
435 })
436 }));
437 let snow_ice_air_fill = Fill::Sampling(Arc::new(|wpos| {
438 Some(match (RandomField::new(0).get(wpos)) % 250 {
439 0..=2 => Block::new(BlockKind::Ice, Rgb::new(120, 160, 255)),
440 3..=5 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
441 6..=10 => Block::new(BlockKind::ArtSnow, Rgb::new(138, 147, 217)),
442 11..=20 => Block::new(BlockKind::ArtSnow, Rgb::new(213, 213, 242)),
443 21..=35 => Block::new(BlockKind::ArtSnow, Rgb::new(231, 230, 247)),
444 36..=62 => Block::new(BlockKind::ArtSnow, Rgb::new(180, 181, 227)),
445 _ => Block::new(BlockKind::ArtSnow, Rgb::new(209, 212, 238)),
446 })
447 }));
448 let bone_fill = Fill::Brick(BlockKind::Misc, Rgb::new(200, 160, 140), 1);
449 let ice_fill = Fill::Block(Block::new(BlockKind::Ice, Rgb::new(120, 160, 255)));
450 let dirt_fill = Fill::Brick(BlockKind::Earth, Rgb::new(55, 25, 8), 24);
451 let grass_fill = Fill::Sampling(Arc::new(|wpos| {
452 Some(match (RandomField::new(0).get(wpos)) % 5 {
453 1 => Block::air(SpriteKind::ShortGrass),
454 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
455 })
456 }));
457 let rock_fill = Fill::Sampling(Arc::new(|wpos| {
458 Some(match (RandomField::new(0).get(wpos)) % 4 {
459 0 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
460 _ => Block::new(BlockKind::Rock, Rgb::new(90, 110, 150)),
461 })
462 }));
463 let bone_shrub = Fill::Sampling(Arc::new(|wpos| {
464 Some(match (RandomField::new(0).get(wpos)) % 40 {
465 0 => Block::new(BlockKind::Misc, Rgb::new(200, 160, 140)),
466 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
467 })
468 }));
469 let yetipit_sprites = Fill::Sampling(Arc::new(|wpos| {
470 Some(match (RandomField::new(0).get(wpos)) % 60 {
471 0..=2 => Block::air(SpriteKind::Bones),
472 4..=5 => Block::air(SpriteKind::GlowIceCrystal),
473 6..=8 => Block::air(SpriteKind::IceCrystal),
474 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
475 })
476 }));
477 let yetipit_sprites_deep = Fill::Sampling(Arc::new(|wpos| {
478 Some(match (RandomField::new(0).get(wpos)) % 275 {
479 0..=8 => Block::air(SpriteKind::Bones),
480 9..=19 => Block::air(SpriteKind::GlowIceCrystal),
481 20..=28 => Block::air(SpriteKind::IceCrystal),
482 29..=30 => Block::air(SpriteKind::DungeonChest1),
483 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
484 })
485 }));
486 let yeti_bones_fill = Fill::Sampling(Arc::new(|wpos| {
487 Some(match (RandomField::new(0).get(wpos)) % 20 {
488 0 => Block::air(SpriteKind::Bones),
489 _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
490 })
491 }));
492 let mut rng = thread_rng();
493
494 let dist: f32 = self.cavern_center.as_().distance(self.entrance.as_());
496 let dir = Dir::from_vec2(self.entrance - self.cavern_center);
497 let tunnel_start: Vec3<f32> = match dir {
498 Dir::X => Vec2::new(self.entrance.x + 7, self.entrance.y),
499 Dir::Y => Vec2::new(self.entrance.x, self.entrance.y + 7),
500 Dir::NegX => Vec2::new(self.entrance.x - 7, self.entrance.y),
501 Dir::NegY => Vec2::new(self.entrance.x, self.entrance.y - 7),
502 }
503 .as_()
504 .with_z(self.cavern_alt - 1.0);
505 let raw_tunnel_end =
507 ((self.cavern_center.as_() - self.entrance.as_()) * self.tunnel_length as f32 / dist)
508 .with_z(self.cavern_alt - 1.0)
509 + self.entrance.as_();
510
511 let offset = 15.0;
512 let tunnel_end = match dir {
513 Dir::X => Vec3::new(raw_tunnel_end.x - offset, tunnel_start.y, raw_tunnel_end.z),
514 Dir::Y => Vec3::new(tunnel_start.x, raw_tunnel_end.y - offset, raw_tunnel_end.z),
515 Dir::NegX => Vec3::new(raw_tunnel_end.x + offset, tunnel_start.y, raw_tunnel_end.z),
516 Dir::NegY => Vec3::new(tunnel_start.x, raw_tunnel_end.y + offset, raw_tunnel_end.z),
517 };
518 painter
520 .line(
521 tunnel_start + Vec3::new(0.0, 0.0, 5.0),
522 {
523 let end = tunnel_start + (dir.to_vec2().as_().with_z(1.0) * 20.0);
524 end.with_z(end.z + 5.0)
525 },
526 10.0,
527 )
528 .clear();
529 painter
530 .sphere(Aabb {
531 min: (self.entrance - 15).with_z(self.cavern_alt as i32 - 15),
532 max: (self.entrance + 15).with_z(self.cavern_alt as i32 + 15),
533 })
534 .fill(snow_ice_fill.clone());
535
536 painter
537 .cylinder(Aabb {
538 min: (self.entrance - 15).with_z(self.cavern_alt as i32),
539 max: (self.entrance + 15).with_z(self.cavern_alt as i32 + 20),
540 })
541 .clear();
542 painter
543 .cylinder(Aabb {
544 min: (self.entrance - 14).with_z(self.cavern_alt as i32 - 1),
545 max: (self.entrance + 14).with_z(self.cavern_alt as i32),
546 })
547 .clear();
548 painter
549 .cylinder(Aabb {
550 min: (self.entrance - 12).with_z(self.cavern_alt as i32 - 40),
551 max: (self.entrance + 12).with_z(self.cavern_alt as i32 - 10),
552 })
553 .fill(snow_ice_fill.clone());
554
555 let valid_entrance = painter.segment_prism(tunnel_start, tunnel_end, 20.0, 30.0);
556 painter
557 .segment_prism(tunnel_start, tunnel_end, 10.0, 10.0)
558 .clear();
559 painter
560 .line(
561 tunnel_start + Vec3::new(0.0, 0.0, 10.0),
562 tunnel_end + Vec3::new(0.0, 0.0, 10.0),
563 10.0,
564 )
565 .clear();
566 painter
567 .line(
568 tunnel_start
569 + match dir {
570 Dir::X => Vec3::new(0.0, 4.0, 7.0),
571 Dir::Y => Vec3::new(4.0, 0.0, 7.0),
572 Dir::NegX => Vec3::new(0.0, 4.0, 7.0),
573 Dir::NegY => Vec3::new(4.0, 0.0, 7.0),
574 },
575 tunnel_end
576 + match dir {
577 Dir::X => Vec3::new(0.0, 4.0, 7.0),
578 Dir::Y => Vec3::new(4.0, 0.0, 7.0),
579 Dir::NegX => Vec3::new(0.0, 4.0, 7.0),
580 Dir::NegY => Vec3::new(4.0, 0.0, 7.0),
581 },
582 8.0,
583 )
584 .intersect(valid_entrance)
585 .clear();
586 painter
587 .line(
588 tunnel_start
589 + match dir {
590 Dir::X => Vec3::new(0.0, -4.0, 7.0),
591 Dir::Y => Vec3::new(-4.0, 0.0, 7.0),
592 Dir::NegX => Vec3::new(0.0, -4.0, 7.0),
593 Dir::NegY => Vec3::new(-4.0, 0.0, 7.0),
594 },
595 tunnel_end
596 + match dir {
597 Dir::X => Vec3::new(0.0, -4.0, 7.0),
598 Dir::Y => Vec3::new(-4.0, 0.0, 7.0),
599 Dir::NegX => Vec3::new(0.0, -4.0, 7.0),
600 Dir::NegY => Vec3::new(-4.0, 0.0, 7.0),
601 },
602 8.0,
603 )
604 .intersect(valid_entrance)
605 .clear();
606 painter
609 .line(
610 tunnel_end.with_z(tunnel_end.z + 4.0),
611 raw_tunnel_end.with_z(raw_tunnel_end.z + 4.0),
612 4.0,
613 )
614 .clear();
615
616 let cavern = painter
618 .sphere_with_radius(
619 self.cavern_center.with_z(self.cavern_alt as i32),
620 self.cavern_radius as f32,
621 )
622 .intersect(painter.aabb(Aabb {
623 min: (self.cavern_center - self.cavern_radius).with_z(self.cavern_alt as i32),
624 max: self.cavern_center.with_z(self.cavern_alt as i32) + self.cavern_radius,
625 }))
626 .sample_with_column({
627 let origin = self.cavern_center.with_z(self.cavern_alt as i32);
628 let radius_sqr = self.cavern_radius.pow(2);
629 move |pos, col| {
630 let alt = col.basement - col.cliff_offset;
631 let sphere_alt = ((radius_sqr - origin.xy().distance_squared(pos.xy())) as f32)
632 .sqrt()
633 + origin.z as f32;
634 let alt = if alt < sphere_alt {
636 alt
637 } else if sphere_alt - alt < 10.0 {
638 f32::lerp(sphere_alt, alt, 1.0 / (alt - sphere_alt).max(1.0))
639 } else {
640 sphere_alt
641 };
642
643 let noise = FastNoise::new(333);
644 let alt_offset = noise.get(pos.with_z(0).as_() / 5.0).powi(2) * 15.0;
645
646 let alt = alt - alt_offset;
647
648 pos.z < alt as i32
649 }
650 });
651 let alt = self.cavern_alt;
652 cavern.clear();
653
654 painter
656 .cylinder(Aabb {
657 min: (self.cavern_center - self.cavern_radius).with_z(alt as i32 - 200),
658 max: (self.cavern_center + self.cavern_radius).with_z(alt as i32),
659 })
660 .fill(snow_ice_fill.clone());
661
662 for (structure, wpos, alt, dir) in self
663 .outer_structures
664 .iter()
665 .map(|(structure, rpos, dir)| {
666 let wpos = rpos + self.entrance;
667 (structure, wpos, land.get_alt_approx(wpos), dir)
668 })
669 .chain(self.cavern_structures.iter().map(|(structure, rpos, dir)| {
670 (structure, rpos + self.cavern_center, self.cavern_alt, dir)
671 }))
672 {
673 match structure {
674 AdletStructure::TunnelEntrance => {
675 let rib_width_curve = |i: f32| 0.5 * (0.4 * i + 1.0).log2() + 5.5;
676 let spine_curve_amplitude = 0.0;
677 let spine_curve_wavelength = 1.0;
678 let spine_curve_function = |i: f32, amplitude: f32, wavelength: f32| {
679 amplitude * (2.0 * PI * (1.0 / wavelength) * i).sin()
680 };
681 let rib_cage_config = RibCageGenerator {
682 dir: *dir,
683 spine_radius: 2.5,
684 length: 40,
685 spine_curve_function,
686 spine_curve_amplitude,
687 spine_curve_wavelength,
688 spine_height: self.cavern_alt + 16.0,
689 spine_start_z_offset: 2.0,
690 spine_ctrl0_z_offset: 3.0,
691 spine_ctrl1_z_offset: 5.0,
692 spine_end_z_offset: 1.0,
693 spine_ctrl0_length_fraction: 0.3,
694 spine_ctrl1_length_fraction: 0.7,
695 rib_base_alt: self.cavern_alt - 1.0,
696 rib_spacing: 7,
697 rib_radius: 1.7,
698 rib_run: 5.0,
699 rib_ctrl0_run_fraction: 0.3,
700 rib_ctrl1_run_fraction: 0.5,
701 rib_ctrl0_width_offset: 5.0,
702 rib_ctrl1_width_offset: 3.0,
703 rib_width_curve,
704 rib_ctrl0_height_fraction: 0.8,
705 rib_ctrl1_height_fraction: 0.4,
706 vertebra_radius: 4.0,
707 vertebra_width: 1.0,
708 vertebra_z_offset: 0.3,
709 };
710 let rib_cage =
711 rib_cage_config.bones(wpos + 40 * dir.opposite().to_vec2(), painter);
712 for bone in rib_cage {
713 bone.fill(bone_fill.clone());
714 }
715 },
716 AdletStructure::Igloo => {
717 let igloo_pos = wpos;
718 let igloo_size = 8.0;
719 let height_handle = 0;
720 let bones_size = igloo_size as i32;
721 painter
722 .cylinder_with_radius(
723 (igloo_pos).with_z(alt as i32 - 5 + height_handle),
724 11.0,
725 45.0,
726 )
727 .clear();
728 let foundation = match RandomField::new(0).get((wpos).with_z(alt as i32)) % 5 {
730 0 => painter
731 .sphere(Aabb {
732 min: (igloo_pos - 15).with_z(alt as i32 - 45 + height_handle),
733 max: (igloo_pos + 15).with_z(alt as i32 - 15 + height_handle),
734 })
735 .union(painter.sphere(Aabb {
736 min: (igloo_pos - 10).with_z(alt as i32 - 20 + height_handle),
737 max: (igloo_pos + 10).with_z(alt as i32 - 5 + height_handle),
738 })),
739 _ => painter
740 .sphere(Aabb {
741 min: (igloo_pos - 15).with_z(alt as i32 - 60 + height_handle),
742 max: (igloo_pos + 15).with_z(alt as i32 - 30 + height_handle),
743 })
744 .union(painter.cone(Aabb {
745 min: (igloo_pos - 15).with_z(alt as i32 - 45 + height_handle),
746 max: (igloo_pos + 15).with_z(alt as i32 + 8 + height_handle),
747 })),
748 };
749 foundation.fill(snow_ice_fill.clone());
750 foundation.intersect(cavern).clear();
751 painter
753 .sphere(Aabb {
754 min: (igloo_pos - 13).with_z(alt as i32 - 11 + height_handle),
755 max: (igloo_pos + 13).with_z(alt as i32 + 11 + height_handle),
756 })
757 .fill(snow_ice_air_fill.clone());
758
759 painter
760 .cylinder(Aabb {
761 min: (igloo_pos - 13).with_z(alt as i32 - 4 + height_handle),
762 max: (igloo_pos + 13).with_z(alt as i32 + 16 + height_handle),
763 })
764 .clear();
765 match RandomField::new(0).get((igloo_pos).with_z(alt as i32)) % 4 {
767 0 => {
768 painter
770 .sphere_with_radius(
771 igloo_pos.with_z(alt as i32 - 1 + height_handle),
772 (igloo_size as i32 - 2) as f32,
773 )
774 .clear();
775 let pos_var = RandomField::new(0).get(igloo_pos.with_z(alt as i32)) % 5;
776 let radius = 8 + pos_var;
777 let bones = 8.0 + pos_var as f32;
778 let phi = TAU / bones;
779 for n in 1..=bones as i32 {
780 let bone_hide_fill = Fill::Sampling(Arc::new(|pos| {
781 Some(match (RandomField::new(0).get(pos)) % 35 {
782 0 => Block::new(BlockKind::Wood, Rgb::new(73, 29, 0)),
783 1 => Block::new(BlockKind::Wood, Rgb::new(78, 67, 43)),
784 2 => Block::new(BlockKind::Wood, Rgb::new(83, 74, 41)),
785 3 => Block::new(BlockKind::Wood, Rgb::new(14, 36, 34)),
786 _ => Block::new(BlockKind::Misc, Rgb::new(200, 160, 140)),
787 })
788 }));
789
790 let pos = Vec2::new(
791 igloo_pos.x + (radius as f32 * ((n as f32 * phi).cos())) as i32,
792 igloo_pos.y + (radius as f32 * ((n as f32 * phi).sin())) as i32,
793 );
794 let bone_var = RandomField::new(0).get(pos.with_z(alt as i32)) % 5;
795
796 match RandomField::new(0).get((igloo_pos - 1).with_z(alt as i32))
797 % 3
798 {
799 0 => {
800 painter
801 .line(
802 pos.with_z(alt as i32 - 6 + height_handle),
803 igloo_pos.with_z(alt as i32 + 8 + height_handle),
804 1.0,
805 )
806 .fill(bone_hide_fill.clone());
807 },
808 _ => {
809 painter
810 .cubic_bezier(
811 pos.with_z(alt as i32 - 6 + height_handle),
812 (pos - ((igloo_pos - pos) / 2)).with_z(
813 alt as i32
814 + 12
815 + bone_var as i32
816 + height_handle,
817 ),
818 (pos + ((igloo_pos - pos) / 2))
819 .with_z(alt as i32 + 9 + height_handle),
820 igloo_pos.with_z(alt as i32 + 5 + height_handle),
821 1.0,
822 )
823 .fill(bone_hide_fill.clone());
824 },
825 };
826 }
827 let outside_wolfs = 2
828 + (RandomField::new(0)
829 .get((igloo_pos - 1).with_z(alt as i32 - 5 + height_handle))
830 % 3) as i32;
831 for _ in 0..outside_wolfs {
832 let igloo_mob_spawn =
833 (igloo_pos - 1).with_z(alt as i32 - 5 + height_handle);
834 painter.spawn(wolf(igloo_mob_spawn.as_(), &mut rng))
835 }
836 },
837 _ => {
838 painter
840 .aabb(Aabb {
841 min: igloo_pos
842 .with_z((alt as i32) + bones_size + height_handle),
843 max: (igloo_pos + 1)
844 .with_z((alt as i32) + bones_size + 3 + height_handle),
845 })
846 .fill(bone_fill.clone());
847 painter
848 .aabb(Aabb {
849 min: Vec2::new(igloo_pos.x, igloo_pos.y - 1)
850 .with_z((alt as i32) + bones_size + 3 + height_handle),
851 max: Vec2::new(igloo_pos.x + 1, igloo_pos.y + 2)
852 .with_z((alt as i32) + bones_size + 4 + height_handle),
853 })
854 .fill(bone_fill.clone());
855 painter
856 .aabb(Aabb {
857 min: igloo_pos
858 .with_z((alt as i32) + bones_size + 3 + height_handle),
859 max: (igloo_pos + 1)
860 .with_z((alt as i32) + bones_size + 4 + height_handle),
861 })
862 .clear();
863 let top_color = Fill::Sampling(Arc::new(|igloo_pos| {
864 Some(match (RandomField::new(0).get(igloo_pos)) % 10 {
865 0 => Block::new(BlockKind::Wood, Rgb::new(73, 29, 0)),
866 1 => Block::new(BlockKind::Wood, Rgb::new(78, 67, 43)),
867 2 => Block::new(BlockKind::Wood, Rgb::new(83, 74, 41)),
868 3 => Block::new(BlockKind::Wood, Rgb::new(14, 36, 34)),
869 _ => Block::new(BlockKind::Rock, Rgb::new(200, 160, 140)),
870 })
871 }));
872 painter
873 .aabb(Aabb {
874 min: (igloo_pos - 1)
875 .with_z((alt as i32) + bones_size - 1 + height_handle),
876 max: (igloo_pos + 2)
877 .with_z((alt as i32) + bones_size + 1 + height_handle),
878 })
879 .fill(top_color.clone());
880 painter
882 .sphere_with_radius(igloo_pos.with_z(alt as i32 - 1), igloo_size)
883 .fill(snow_ice_fill.clone());
884 for dir in CARDINALS {
886 let hide_size = 5
887 + (RandomField::new(0)
888 .get((igloo_pos + dir).with_z(alt as i32))
889 % 4);
890 let hide_color = match RandomField::new(0)
891 .get((igloo_pos + dir).with_z(alt as i32))
892 % 4
893 {
894 0 => Fill::Block(Block::new(
895 BlockKind::Wood,
896 Rgb::new(73, 29, 0),
897 )),
898 1 => Fill::Block(Block::new(
899 BlockKind::Wood,
900 Rgb::new(78, 67, 43),
901 )),
902 2 => Fill::Block(Block::new(
903 BlockKind::Wood,
904 Rgb::new(83, 74, 41),
905 )),
906 _ => Fill::Block(Block::new(
907 BlockKind::Wood,
908 Rgb::new(14, 36, 34),
909 )),
910 };
911 painter
912 .sphere_with_radius(
913 (igloo_pos + (2 * dir))
914 .with_z((alt as i32) + 1 + height_handle),
915 hide_size as f32,
916 )
917 .fill(hide_color.clone());
918 }
919 painter
921 .sphere_with_radius(
922 igloo_pos.with_z(alt as i32 - 1 + height_handle),
923 (igloo_size as i32 - 2) as f32,
924 )
925 .clear();
926 painter
928 .aabb(Aabb {
929 min: Vec2::new(
930 igloo_pos.x - 1,
931 igloo_pos.y - igloo_size as i32 - 2,
932 )
933 .with_z(alt as i32 - 4 + height_handle),
934 max: Vec2::new(
935 igloo_pos.x + 1,
936 igloo_pos.y + igloo_size as i32 + 2,
937 )
938 .with_z(alt as i32 - 2 + height_handle),
939 })
940 .clear();
941 painter
942 .aabb(Aabb {
943 min: Vec2::new(
944 igloo_pos.x - igloo_size as i32 - 2,
945 igloo_pos.y - 1,
946 )
947 .with_z(alt as i32 - 4 + height_handle),
948 max: Vec2::new(
949 igloo_pos.x + igloo_size as i32 + 2,
950 igloo_pos.y + 1,
951 )
952 .with_z(alt as i32 - 2 + height_handle),
953 })
954 .clear();
955 for h in 0..(bones_size + 4) {
957 painter
958 .line(
959 (igloo_pos - bones_size)
960 .with_z((alt as i32) - 5 + h + height_handle),
961 (igloo_pos + bones_size)
962 .with_z((alt as i32) - 5 + h + height_handle),
963 0.5,
964 )
965 .intersect(painter.sphere_with_radius(
966 igloo_pos.with_z((alt as i32) - 2 + height_handle),
967 9.0,
968 ))
969 .fill(bone_fill.clone());
970
971 painter
972 .line(
973 Vec2::new(
974 igloo_pos.x - bones_size,
975 igloo_pos.y + bones_size,
976 )
977 .with_z((alt as i32) - 4 + h + height_handle),
978 Vec2::new(
979 igloo_pos.x + bones_size,
980 igloo_pos.y - bones_size,
981 )
982 .with_z((alt as i32) - 4 + h + height_handle),
983 0.5,
984 )
985 .intersect(painter.sphere_with_radius(
986 igloo_pos.with_z((alt as i32) - 2 + height_handle),
987 9.0,
988 ))
989 .fill(bone_fill.clone());
990 }
991 painter
992 .sphere_with_radius(
993 igloo_pos.with_z((alt as i32) - 2 + height_handle),
994 5.0,
995 )
996 .clear();
997
998 painter.rotated_sprite(
1000 Vec2::new(
1001 igloo_pos.x - bones_size + 4,
1002 igloo_pos.y + bones_size - 5,
1003 )
1004 .with_z((alt as i32) - 1 + height_handle),
1005 SpriteKind::WallSconce,
1006 0_u8,
1007 );
1008 let igloo_mobs = 1
1009 + (RandomField::new(0)
1010 .get((igloo_pos - 1).with_z(alt as i32 - 5 + height_handle))
1011 % 2) as i32;
1012
1013 for _ in 0..igloo_mobs {
1014 let igloo_mob_spawn =
1015 (igloo_pos - 1).with_z(alt as i32 - 5 + height_handle);
1016 painter.spawn(random_adlet(igloo_mob_spawn.as_(), &mut rng));
1017 }
1018 },
1019 };
1020 painter
1022 .cylinder_with_radius(
1023 (igloo_pos).with_z(alt as i32 - 7 + height_handle),
1024 (igloo_size as i32 - 4) as f32,
1025 2.0,
1026 )
1027 .fill(snow_ice_fill.clone());
1028
1029 painter.sprite(
1031 igloo_pos.with_z(alt as i32 - 5 + height_handle),
1032 SpriteKind::FireBowlGround,
1033 );
1034 },
1035 AdletStructure::SpeleothemCluster => {
1036 let layer_color = Fill::Sampling(Arc::new(|wpos| {
1037 Some(
1038 match (RandomField::new(0).get(Vec3::new(wpos.z, 0, 0))) % 6 {
1039 0 => Block::new(BlockKind::Rock, Rgb::new(100, 128, 179)),
1040 1 => Block::new(BlockKind::Rock, Rgb::new(95, 127, 178)),
1041 2 => Block::new(BlockKind::Rock, Rgb::new(101, 121, 169)),
1042 3 => Block::new(BlockKind::Rock, Rgb::new(61, 109, 145)),
1043 4 => Block::new(BlockKind::Rock, Rgb::new(74, 128, 168)),
1044 _ => Block::new(BlockKind::Rock, Rgb::new(69, 123, 162)),
1045 },
1046 )
1047 }));
1048 for dir in NEIGHBORS {
1049 let cone_radius = 3
1050 + (RandomField::new(0).get((wpos + dir).with_z(alt as i32)) % 3) as i32;
1051 let cone_offset = 3
1052 + (RandomField::new(0).get((wpos + 1 + dir).with_z(alt as i32)) % 4)
1053 as i32;
1054 let cone_height = 15
1055 + (RandomField::new(0).get((wpos + 2 + dir).with_z(alt as i32)) % 50)
1056 as i32;
1057 painter
1059 .cone_with_radius(
1060 (wpos + (dir * cone_offset)).with_z(alt as i32 - (cone_height / 8)),
1061 cone_radius as f32,
1062 cone_height as f32,
1063 )
1064 .fill(layer_color.clone());
1065 let top_pos = (RandomField::new(0).get((wpos + 3 + dir).with_z(alt as i32))
1067 % 2) as i32;
1068 painter
1069 .aabb(Aabb {
1070 min: (wpos + (dir * cone_offset) - top_pos).with_z(alt as i32),
1071 max: (wpos + (dir * cone_offset) + 1 - top_pos)
1072 .with_z((alt as i32) + cone_height - (cone_height / 6)),
1073 })
1074 .fill(layer_color.clone());
1075 }
1076 },
1077 AdletStructure::Bonfire => {
1078 let bonfire_pos = wpos;
1079 let fire_fill = Fill::Sampling(Arc::new(|bonfire_pos| {
1080 Some(match (RandomField::new(0).get(bonfire_pos)) % 200 {
1081 0 => Block::air(SpriteKind::Ember),
1082 _ => Block::air(SpriteKind::FireBlock),
1083 })
1084 }));
1085 let fire_pos = bonfire_pos.with_z(alt as i32 + 2);
1086 lazy_static! {
1087 pub static ref FIRE: AssetHandle<StructuresGroup> =
1088 PrefabStructure::load_group("site_structures.adlet.bonfire");
1089 }
1090 let fire_rng = RandomField::new(0).get(fire_pos) % 10;
1091 let fire = FIRE.read();
1092 let fire = fire[fire_rng as usize % fire.len()].clone();
1093 painter
1094 .prim(Primitive::Prefab(Box::new(fire.clone())))
1095 .translate(fire_pos)
1096 .fill(Fill::Prefab(Box::new(fire), fire_pos, fire_rng));
1097 painter
1098 .sphere_with_radius((bonfire_pos).with_z(alt as i32 + 5), 4.0)
1099 .fill(fire_fill.clone());
1100 painter
1101 .cylinder_with_radius((bonfire_pos).with_z(alt as i32 + 2), 6.5, 1.0)
1102 .fill(fire_fill);
1103 },
1104 AdletStructure::YetiPit => {
1105 let yetipit_center = self.cavern_center;
1106 let yetipit_entrance_pos = wpos;
1107 let storeys = (3 + RandomField::new(0).get((yetipit_center).with_z(alt as i32))
1108 % 2) as i32;
1109 for s in 0..storeys {
1110 let down = 10_i32;
1111 let level = (alt as i32) - 50 - (s * (3 * down));
1112 let room_size = (25
1113 + RandomField::new(0).get((yetipit_center * s).with_z(level)) % 5)
1114 as i32;
1115 let sprites_fill = match s {
1116 0..=1 => yetipit_sprites.clone(),
1117 _ => yetipit_sprites_deep.clone(),
1118 };
1119 if s == (storeys - 1) {
1120 painter
1122 .cylinder_with_radius(
1123 yetipit_center.with_z(level - (3 * down) - 5),
1124 room_size as f32,
1125 ((room_size / 3) + 5) as f32,
1126 )
1127 .clear();
1128 painter
1129 .cylinder_with_radius(
1130 yetipit_center.with_z(level - (3 * down) - 6),
1131 room_size as f32,
1132 1.0,
1133 )
1134 .fill(snow_ice_fill.clone());
1135 for r in 0..4 {
1137 painter
1138 .cylinder_with_radius(
1139 yetipit_center.with_z(level - (3 * down) - 2 - r),
1140 (room_size + 2 - r) as f32,
1141 1.0,
1142 )
1143 .fill(snow_ice_fill.clone());
1144 painter
1145 .cylinder_with_radius(
1146 yetipit_center.with_z(level - (3 * down) - 1 - r),
1147 (room_size - r) as f32,
1148 1.0,
1149 )
1150 .fill(sprites_fill.clone());
1151 painter
1152 .cylinder_with_radius(
1153 yetipit_center.with_z(level - (3 * down) - 2 - r),
1154 (room_size - 1 - r) as f32,
1155 2.0,
1156 )
1157 .clear();
1158 }
1159 painter
1160 .cylinder_with_radius(
1161 yetipit_center.with_z(level - (3 * down) - 5),
1162 (room_size - 4) as f32,
1163 1.0,
1164 )
1165 .fill(yeti_bones_fill.clone());
1166 painter
1167 .cone_with_radius(
1168 yetipit_center.with_z(level - (3 * down) + (room_size / 3) - 2),
1169 room_size as f32,
1170 (room_size / 3) as f32,
1171 )
1172 .clear();
1173 for dir in NEIGHBORS {
1175 let cluster_pos = yetipit_center + dir * room_size - 3;
1176 for dir in NEIGHBORS3 {
1177 let cone_radius = 3
1178 + (RandomField::new(0)
1179 .get((cluster_pos + dir).with_z(alt as i32))
1180 % 3) as i32;
1181 let cone_offset = 3
1182 + (RandomField::new(0)
1183 .get((cluster_pos + 1 + dir).with_z(alt as i32))
1184 % 4) as i32;
1185 let cone_height = 15
1186 + (RandomField::new(0)
1187 .get((cluster_pos + 2 + dir).with_z(alt as i32))
1188 % 10) as i32;
1189 painter
1191 .cone_with_radius(
1192 (cluster_pos + (dir * cone_offset))
1193 .with_z(level - (3 * down) - 4 - (cone_height / 8)),
1194 cone_radius as f32,
1195 cone_height as f32,
1196 )
1197 .fill(snow_ice_fill.clone());
1198 let top_pos = (RandomField::new(0)
1200 .get((cluster_pos + 3 + dir).with_z(level))
1201 % 2)
1202 as i32;
1203 painter
1204 .aabb(Aabb {
1205 min: (cluster_pos + (dir * cone_offset) - top_pos)
1206 .with_z(level - (3 * down) - 3),
1207 max: (cluster_pos + (dir * cone_offset) + 1 - top_pos)
1208 .with_z(
1209 (level - (3 * down) - 2) + cone_height
1210 - (cone_height / 6)
1211 + 3,
1212 ),
1213 })
1214 .fill(snow_ice_fill.clone());
1215 }
1216 }
1217 for dir in NEIGHBORS {
1219 for c in 0..8 {
1220 let cluster_pos = yetipit_center + dir * (c * (room_size / 5));
1221 for dir in NEIGHBORS3 {
1222 let cone_radius = 3
1223 + (RandomField::new(0)
1224 .get((cluster_pos + dir).with_z(alt as i32))
1225 % 3)
1226 as i32;
1227 let cone_offset = 3
1228 + (RandomField::new(0)
1229 .get((cluster_pos + 1 + dir).with_z(alt as i32))
1230 % 4)
1231 as i32;
1232 let cone_height = 15
1233 + (RandomField::new(0)
1234 .get((cluster_pos + 2 + dir).with_z(alt as i32))
1235 % 10)
1236 as i32;
1237 painter
1239 .cone_with_radius(
1240 (cluster_pos + (dir * cone_offset)).with_z(
1241 level - (3 * down) - 4 - (cone_height / 8),
1242 ),
1243 cone_radius as f32,
1244 (cone_height - 1) as f32,
1245 )
1246 .rotate_about(
1247 Mat3::rotation_x(PI).as_(),
1248 yetipit_center.with_z(level - (2 * down) - 1),
1249 )
1250 .fill(snow_ice_fill.clone());
1251 let top_pos = (RandomField::new(0)
1253 .get((cluster_pos + 3 + dir).with_z(level))
1254 % 2)
1255 as i32;
1256 painter
1257 .aabb(Aabb {
1258 min: (cluster_pos + (dir * cone_offset) - top_pos)
1259 .with_z(level - (3 * down) - 3),
1260 max: (cluster_pos + (dir * cone_offset) + 1
1261 - top_pos)
1262 .with_z(
1263 (level - (3 * down) - 2) + cone_height
1264 - (cone_height / 6)
1265 - 2,
1266 ),
1267 })
1268 .rotate_about(
1269 Mat3::rotation_x(PI).as_(),
1270 yetipit_center.with_z(level - (2 * down)),
1271 )
1272 .fill(snow_ice_fill.clone());
1273 }
1274 }
1275 }
1276 for dir in NEIGHBORS3 {
1278 let pond_radius = (RandomField::new(0)
1279 .get((yetipit_center + dir).with_z(alt as i32))
1280 % 8) as i32;
1281 let pond_pos =
1282 yetipit_center + (dir * ((room_size / 4) + (pond_radius)));
1283 painter
1284 .cylinder_with_radius(
1285 pond_pos.with_z(level - (3 * down) - 6),
1286 pond_radius as f32,
1287 1.0,
1288 )
1289 .fill(ice_fill.clone());
1290 }
1291 let yeti_spawn = yetipit_center.with_z(level - (3 * down) - 4);
1293 painter.spawn(yeti(yeti_spawn.as_(), &mut rng));
1294 } else {
1295 painter
1297 .cylinder_with_radius(
1298 yetipit_center.with_z(level - (3 * down) - 5),
1299 room_size as f32,
1300 ((room_size / 3) + 5) as f32,
1301 )
1302 .clear();
1303 for r in 0..4 {
1305 painter
1306 .cylinder_with_radius(
1307 yetipit_center.with_z(level - (3 * down) - 2 - r),
1308 (room_size + 2 - r) as f32,
1309 1.0,
1310 )
1311 .fill(snow_ice_fill.clone());
1312 painter
1313 .cylinder_with_radius(
1314 yetipit_center.with_z(level - (3 * down) - 1 - r),
1315 (room_size - r) as f32,
1316 1.0,
1317 )
1318 .fill(sprites_fill.clone());
1319 painter
1320 .cylinder_with_radius(
1321 yetipit_center.with_z(level - (3 * down) - 2 - r),
1322 (room_size - 1 - r) as f32,
1323 2.0,
1324 )
1325 .clear();
1326 }
1327 let yetipit_mobs = 1
1328 + (RandomField::new(0)
1329 .get(yetipit_center.with_z(level - (3 * down) - 3))
1330 % 2) as i32;
1331 for _ in 0..yetipit_mobs {
1332 let yetipit_mob_spawn =
1333 yetipit_center.with_z(level - (3 * down) - 3);
1334 painter
1335 .spawn(random_yetipit_mob(yetipit_mob_spawn.as_(), &mut rng));
1336 }
1337 painter
1338 .cone_with_radius(
1339 yetipit_center.with_z(level - (3 * down) + (room_size / 3) - 2),
1340 room_size as f32,
1341 (room_size / 3) as f32,
1342 )
1343 .clear();
1344 for dir in NEIGHBORS {
1346 let cluster_pos = yetipit_center + dir * room_size;
1347 for dir in NEIGHBORS {
1348 let cone_radius = 3
1349 + (RandomField::new(0)
1350 .get((cluster_pos + dir).with_z(alt as i32))
1351 % 3) as i32;
1352 let cone_offset = 3
1353 + (RandomField::new(0)
1354 .get((cluster_pos + 1 + dir).with_z(alt as i32))
1355 % 4) as i32;
1356 let cone_height = 15
1357 + (RandomField::new(0)
1358 .get((cluster_pos + 2 + dir).with_z(alt as i32))
1359 % 10) as i32;
1360 painter
1362 .cone_with_radius(
1363 (cluster_pos + (dir * cone_offset))
1364 .with_z(level - (3 * down) - 4 - (cone_height / 8)),
1365 cone_radius as f32,
1366 cone_height as f32,
1367 )
1368 .fill(snow_ice_fill.clone());
1369 let top_pos = (RandomField::new(0)
1371 .get((cluster_pos + 3 + dir).with_z(level))
1372 % 2)
1373 as i32;
1374 painter
1375 .aabb(Aabb {
1376 min: (cluster_pos + (dir * cone_offset) - top_pos)
1377 .with_z(level - (3 * down) - 3),
1378 max: (cluster_pos + (dir * cone_offset) + 1 - top_pos)
1379 .with_z(
1380 (level - (3 * down) - 2) + cone_height
1381 - (cone_height / 6)
1382 + 3,
1383 ),
1384 })
1385 .fill(snow_ice_fill.clone());
1386 }
1387 }
1388 for dir in NEIGHBORS {
1390 for c in 0..5 {
1391 let cluster_pos = yetipit_center + dir * (c * (room_size / 3));
1392 for dir in NEIGHBORS {
1393 let cone_radius = 3
1394 + (RandomField::new(0)
1395 .get((cluster_pos + dir).with_z(alt as i32))
1396 % 3)
1397 as i32;
1398 let cone_offset = 3
1399 + (RandomField::new(0)
1400 .get((cluster_pos + 1 + dir).with_z(alt as i32))
1401 % 4)
1402 as i32;
1403 let cone_height = 15
1404 + (RandomField::new(0)
1405 .get((cluster_pos + 2 + dir).with_z(alt as i32))
1406 % 10)
1407 as i32;
1408 painter
1410 .cone_with_radius(
1411 (cluster_pos + (dir * cone_offset)).with_z(
1412 level - (3 * down) - 4 - (cone_height / 8),
1413 ),
1414 cone_radius as f32,
1415 (cone_height - 1) as f32,
1416 )
1417 .rotate_about(
1418 Mat3::rotation_x(PI).as_(),
1419 yetipit_center.with_z(level - (2 * down) - 1),
1420 )
1421 .fill(snow_ice_fill.clone());
1422 let top_pos = (RandomField::new(0)
1424 .get((cluster_pos + 3 + dir).with_z(level))
1425 % 2)
1426 as i32;
1427 painter
1428 .aabb(Aabb {
1429 min: (cluster_pos + (dir * cone_offset) - top_pos)
1430 .with_z(level - (3 * down) - 3),
1431 max: (cluster_pos + (dir * cone_offset) + 1
1432 - top_pos)
1433 .with_z(
1434 (level - (3 * down) - 2) + cone_height
1435 - (cone_height / 6)
1436 - 2,
1437 ),
1438 })
1439 .rotate_about(
1440 Mat3::rotation_x(PI).as_(),
1441 yetipit_center.with_z(level - (2 * down)),
1442 )
1443 .fill(snow_ice_fill.clone());
1444 }
1445 }
1446 }
1447 painter
1449 .cylinder_with_radius(
1450 yetipit_center.with_z(level - (3 * down) - 4),
1451 (room_size / 8) as f32,
1452 1.0,
1453 )
1454 .fill(ice_fill.clone());
1455 }
1456 let tunnels = (2 + RandomField::new(0)
1457 .get((yetipit_center + s).with_z(level))
1458 % 2) as i32;
1459 for t in 1..tunnels {
1460 let away1 = (50
1461 + RandomField::new(0).get((yetipit_center * (s + t)).with_z(level))
1462 % 20) as i32;
1463 let away2 = (50
1464 + RandomField::new(0)
1465 .get((yetipit_center * (s + (2 * t))).with_z(level))
1466 % 20) as i32;
1467 let away3 = (50
1468 + RandomField::new(0)
1469 .get((yetipit_center * (s + (3 * t))).with_z(level))
1470 % 20) as i32;
1471 let away4 = (50
1472 + RandomField::new(0)
1473 .get((yetipit_center * (s + (4 * t))).with_z(level))
1474 % 20) as i32;
1475
1476 let dir1 = 1 - 2
1477 * (RandomField::new(0).get((yetipit_center).with_z(t * level)) % 2)
1478 as i32;
1479 let dir2 = 1 - 2
1480 * (RandomField::new(0)
1481 .get((yetipit_center).with_z((2 * t) * level))
1482 % 2) as i32;
1483 painter
1485 .cubic_bezier(
1486 yetipit_center.with_z(level - 3),
1487 Vec2::new(
1488 yetipit_center.x + ((away1 + (s * (down / 4))) * dir1),
1489 yetipit_center.y + ((away2 + (s * (down / 4))) * dir2),
1490 )
1491 .with_z(level - (2 * down)),
1492 Vec2::new(
1493 yetipit_center.x + ((away3 + (s * (down / 4))) * dir1),
1494 yetipit_center.y + ((away4 + (s * (down / 4))) * dir2),
1495 )
1496 .with_z(level - (3 * down)),
1497 yetipit_center.with_z(level - (3 * down)),
1498 6.0,
1499 )
1500 .clear();
1501 }
1502 }
1503 painter
1506 .sphere(Aabb {
1507 min: (yetipit_entrance_pos - 8).with_z(alt as i32 - 8),
1508 max: (yetipit_entrance_pos + 8).with_z(alt as i32 + 8),
1509 })
1510 .fill(rock_fill.clone());
1511 painter
1513 .cylinder(Aabb {
1514 min: (yetipit_entrance_pos - 8).with_z(alt as i32 - 20),
1515 max: (yetipit_entrance_pos + 8).with_z(alt as i32),
1516 })
1517 .fill(snow_ice_fill.clone());
1518 let door_dist = self.cavern_center - yetipit_entrance_pos;
1520 let door_dir = door_dist.map(|e| e.checked_div(e.abs()).unwrap_or(0));
1521 painter
1522 .cubic_bezier(
1523 (yetipit_entrance_pos + door_dir * 10).with_z(alt as i32 + 2),
1524 (yetipit_entrance_pos - door_dir * 16).with_z(alt as i32 - 10),
1525 (yetipit_entrance_pos + door_dir * 20).with_z((alt as i32) - 30),
1526 self.cavern_center.with_z((alt as i32) - 50),
1527 4.0,
1528 )
1529 .clear();
1530 painter
1532 .cylinder(Aabb {
1533 min: Vec2::new(yetipit_entrance_pos.x - 7, yetipit_entrance_pos.y - 7)
1534 .with_z(alt as i32 - 8),
1535 max: Vec2::new(yetipit_entrance_pos.x + 7, yetipit_entrance_pos.y + 7)
1536 .with_z((alt as i32) - 7),
1537 })
1538 .fill(snow_ice_fill.clone());
1539
1540 painter
1541 .cylinder(Aabb {
1542 min: Vec2::new(yetipit_entrance_pos.x - 3, yetipit_entrance_pos.y - 3)
1543 .with_z(alt as i32 - 8),
1544 max: Vec2::new(yetipit_entrance_pos.x + 3, yetipit_entrance_pos.y + 3)
1545 .with_z((alt as i32) - 7),
1546 })
1547 .fill(Fill::Block(Block::air(SpriteKind::BoneKeyDoor)));
1548 painter
1549 .aabb(Aabb {
1550 min: Vec2::new(yetipit_entrance_pos.x - 1, yetipit_entrance_pos.y)
1551 .with_z(alt as i32 - 8),
1552 max: Vec2::new(yetipit_entrance_pos.x, yetipit_entrance_pos.y + 1)
1553 .with_z((alt as i32) - 7),
1554 })
1555 .fill(Fill::Block(Block::air(SpriteKind::BoneKeyhole)));
1556 },
1557 AdletStructure::Tannery => {
1558 painter
1560 .cylinder_with_radius(wpos.with_z(alt as i32), 7.0, 1.0)
1561 .fill(bone_shrub.clone());
1562 painter
1564 .aabb(Aabb {
1565 min: Vec2::new(wpos.x - 6, wpos.y).with_z(alt as i32),
1566 max: Vec2::new(wpos.x + 6, wpos.y + 1).with_z((alt as i32) + 8),
1567 })
1568 .fill(bone_fill.clone());
1569 painter
1570 .aabb(Aabb {
1571 min: Vec2::new(wpos.x - 5, wpos.y).with_z(alt as i32),
1572 max: Vec2::new(wpos.x + 5, wpos.y + 1).with_z((alt as i32) + 8),
1573 })
1574 .clear();
1575 painter
1576 .aabb(Aabb {
1577 min: Vec2::new(wpos.x - 6, wpos.y - 1).with_z(alt as i32 + 8),
1578 max: Vec2::new(wpos.x + 6, wpos.y + 2).with_z((alt as i32) + 9),
1579 })
1580 .fill(bone_fill.clone());
1581 painter
1582 .aabb(Aabb {
1583 min: Vec2::new(wpos.x - 5, wpos.y - 1).with_z(alt as i32 + 8),
1584 max: Vec2::new(wpos.x + 5, wpos.y + 2).with_z((alt as i32) + 9),
1585 })
1586 .clear();
1587 painter
1588 .aabb(Aabb {
1589 min: Vec2::new(wpos.x - 6, wpos.y).with_z(alt as i32 + 8),
1590 max: Vec2::new(wpos.x + 6, wpos.y + 1).with_z((alt as i32) + 9),
1591 })
1592 .clear();
1593 painter
1595 .aabb(Aabb {
1596 min: Vec2::new(wpos.x - 6, wpos.y + 3).with_z(alt as i32),
1597 max: Vec2::new(wpos.x + 6, wpos.y + 4).with_z((alt as i32) + 2),
1598 })
1599 .fill(bone_fill.clone());
1600 painter
1601 .aabb(Aabb {
1602 min: Vec2::new(wpos.x - 5, wpos.y + 3).with_z(alt as i32),
1603 max: Vec2::new(wpos.x - 3, wpos.y + 4).with_z((alt as i32) + 1),
1604 })
1605 .clear();
1606 painter
1607 .aabb(Aabb {
1608 min: Vec2::new(wpos.x + 3, wpos.y + 3).with_z(alt as i32),
1609 max: Vec2::new(wpos.x + 5, wpos.y + 4).with_z((alt as i32) + 1),
1610 })
1611 .clear();
1612 painter
1613 .aabb(Aabb {
1614 min: Vec2::new(wpos.x - 2, wpos.y + 3).with_z(alt as i32),
1615 max: Vec2::new(wpos.x + 2, wpos.y + 4).with_z((alt as i32) + 1),
1616 })
1617 .clear();
1618 for n in 0..10 {
1620 let hide_color =
1621 match RandomField::new(0).get((wpos + n).with_z(alt as i32)) % 4 {
1622 0 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(73, 29, 0))),
1623 1 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(78, 67, 43))),
1624 2 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(83, 74, 41))),
1625 _ => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(14, 36, 34))),
1626 };
1627 let rand_length =
1628 (RandomField::new(0).get((wpos - n).with_z(alt as i32)) % 7) as i32;
1629 painter
1630 .aabb(Aabb {
1631 min: Vec2::new(wpos.x - 5, wpos.y).with_z(alt as i32 + rand_length),
1632 max: Vec2::new(wpos.x - 4 + n, wpos.y + 1).with_z((alt as i32) + 8),
1633 })
1634 .fill(hide_color.clone());
1635 }
1636 let tannery_mobs =
1637 1 + (RandomField::new(0).get(wpos.with_z(alt as i32)) % 2) as i32;
1638 for _ in 0..tannery_mobs {
1639 let tannery_mob_spawn = wpos.with_z(alt as i32);
1640 painter.spawn(random_adlet(tannery_mob_spawn.as_(), &mut rng));
1641 }
1642 },
1643 AdletStructure::AnimalPen => {
1644 let pen_size = 8.0;
1645 painter
1646 .sphere_with_radius(
1647 wpos.with_z(alt as i32 + (pen_size as i32 / 4) - 1),
1648 pen_size as f32,
1649 )
1650 .fill(bone_fill.clone());
1651 painter
1652 .sphere_with_radius(
1653 wpos.with_z(alt as i32 + (pen_size as i32 / 2) - 1),
1654 pen_size as f32,
1655 )
1656 .clear();
1657 painter
1658 .cylinder(Aabb {
1659 min: (wpos - (pen_size as i32)).with_z(alt as i32 - (pen_size as i32)),
1660 max: (wpos + (pen_size as i32)).with_z(alt as i32),
1661 })
1662 .fill(dirt_fill.clone());
1663 painter
1664 .cylinder(Aabb {
1665 min: (wpos - (pen_size as i32) + 1).with_z(alt as i32),
1666 max: (wpos + (pen_size as i32) - 1).with_z(alt as i32 + 1),
1667 })
1668 .fill(grass_fill.clone());
1669 enum AnimalPenKind {
1670 Rat,
1671 Wolf,
1672 Bear,
1673 }
1674 let (kind, num) = {
1675 let rand_field = RandomField::new(1).get(wpos.with_z(alt as i32));
1676 match RandomField::new(0).get(wpos.with_z(alt as i32)) % 4 {
1677 0 => (AnimalPenKind::Bear, 1 + rand_field % 2),
1678 1 => (AnimalPenKind::Wolf, 2 + rand_field % 2),
1679 _ => (AnimalPenKind::Rat, 3 + rand_field % 3),
1680 }
1681 };
1682 for _ in 0..num {
1683 let animalpen_mob_spawn = wpos.with_z(alt as i32);
1684 match kind {
1685 AnimalPenKind::Rat => {
1686 painter.spawn(rat(animalpen_mob_spawn.as_(), &mut rng))
1687 },
1688 AnimalPenKind::Wolf => {
1689 painter.spawn(wolf(animalpen_mob_spawn.as_(), &mut rng))
1690 },
1691 AnimalPenKind::Bear => {
1692 painter.spawn(bear(animalpen_mob_spawn.as_(), &mut rng))
1693 },
1694 }
1695 }
1696 },
1697 AdletStructure::CookFire => {
1698 painter
1699 .cylinder(Aabb {
1700 min: (wpos - 3).with_z(alt as i32),
1701 max: (wpos + 4).with_z(alt as i32 + 1),
1702 })
1703 .fill(bone_fill.clone());
1704 let cook_sprites = Fill::Sampling(Arc::new(|wpos| {
1705 Some(match (RandomField::new(0).get(wpos)) % 20 {
1706 0 => Block::air(SpriteKind::Pot),
1707 1 => Block::air(SpriteKind::Bowl),
1708 2 => Block::air(SpriteKind::Pot),
1709 3 => Block::air(SpriteKind::VialEmpty),
1710 4 => Block::air(SpriteKind::Lantern),
1711 _ => Block::air(SpriteKind::Empty),
1712 })
1713 }));
1714 painter
1715 .cylinder(Aabb {
1716 min: (wpos - 3).with_z(alt as i32 + 1),
1717 max: (wpos + 4).with_z(alt as i32 + 2),
1718 })
1719 .fill(cook_sprites);
1720 painter
1721 .cylinder(Aabb {
1722 min: (wpos - 2).with_z(alt as i32),
1723 max: (wpos + 3).with_z(alt as i32 + 2),
1724 })
1725 .clear();
1726 painter
1727 .aabb(Aabb {
1728 min: (wpos).with_z(alt as i32),
1729 max: (wpos + 1).with_z(alt as i32 + 1),
1730 })
1731 .fill(bone_fill.clone());
1732 painter.sprite(wpos.with_z(alt as i32 + 1), SpriteKind::FireBowlGround);
1733 let cookfire_mobs =
1734 1 + (RandomField::new(0).get(wpos.with_z(alt as i32)) % 2) as i32;
1735 for _ in 0..cookfire_mobs {
1736 let cookfire_mob_spawn = wpos.with_z(alt as i32);
1737 painter.spawn(random_adlet(cookfire_mob_spawn.as_(), &mut rng));
1738 }
1739 },
1740 AdletStructure::RockHut => {
1741 painter
1742 .sphere_with_radius(wpos.with_z(alt as i32), 5.0)
1743 .fill(rock_fill.clone());
1744 painter
1745 .sphere_with_radius(wpos.with_z(alt as i32), 4.0)
1746 .clear();
1747 painter
1749 .aabb(Aabb {
1750 min: Vec2::new(wpos.x - 6, wpos.y - 1).with_z(alt as i32),
1751 max: Vec2::new(wpos.x + 6, wpos.y + 1).with_z(alt as i32 + 2),
1752 })
1753 .clear();
1754 painter
1755 .aabb(Aabb {
1756 min: Vec2::new(wpos.x - 1, wpos.y - 6).with_z(alt as i32),
1757 max: Vec2::new(wpos.x + 1, wpos.y + 6).with_z(alt as i32 + 2),
1758 })
1759 .clear();
1760 painter
1762 .cylinder(Aabb {
1763 min: (wpos - 5).with_z((alt as i32) - 5),
1764 max: (wpos + 5).with_z(alt as i32 - 1),
1765 })
1766 .fill(dirt_fill.clone());
1767 painter.sprite(wpos.with_z(alt as i32) - 1, SpriteKind::FireBowlGround);
1768 let rockhut_mobs =
1769 1 + (RandomField::new(0).get(wpos.with_z(alt as i32)) % 2) as i32;
1770 for _ in 0..rockhut_mobs {
1771 let rockhut_mob_spawn = wpos.with_z(alt as i32);
1772 painter.spawn(random_adlet(rockhut_mob_spawn.as_(), &mut rng));
1773 }
1774 },
1775 AdletStructure::BoneHut => {
1776 let hut_radius = 5;
1777 painter
1779 .aabb(Aabb {
1780 min: wpos.with_z((alt as i32) + hut_radius + 4),
1781 max: (wpos + 1).with_z((alt as i32) + hut_radius + 7),
1782 })
1783 .fill(bone_fill.clone());
1784 painter
1785 .aabb(Aabb {
1786 min: Vec2::new(wpos.x, wpos.y - 1)
1787 .with_z((alt as i32) + hut_radius + 7),
1788 max: Vec2::new(wpos.x + 1, wpos.y + 2)
1789 .with_z((alt as i32) + hut_radius + 8),
1790 })
1791 .fill(bone_fill.clone());
1792 painter
1793 .aabb(Aabb {
1794 min: wpos.with_z((alt as i32) + hut_radius + 7),
1795 max: (wpos + 1).with_z((alt as i32) + hut_radius + 8),
1796 })
1797 .clear();
1798 let top_color = Fill::Sampling(Arc::new(|wpos| {
1799 Some(match (RandomField::new(0).get(wpos)) % 10 {
1800 0 => Block::new(BlockKind::Wood, Rgb::new(73, 29, 0)),
1801 1 => Block::new(BlockKind::Wood, Rgb::new(78, 67, 43)),
1802 2 => Block::new(BlockKind::Wood, Rgb::new(83, 74, 41)),
1803 3 => Block::new(BlockKind::Wood, Rgb::new(14, 36, 34)),
1804 _ => Block::new(BlockKind::Rock, Rgb::new(200, 160, 140)),
1805 })
1806 }));
1807 painter
1808 .aabb(Aabb {
1809 min: (wpos - 1).with_z((alt as i32) + hut_radius + 3),
1810 max: (wpos + 2).with_z((alt as i32) + hut_radius + 5),
1811 })
1812 .fill(top_color.clone());
1813 for dir in CARDINALS {
1815 let hide_size =
1816 6 + (RandomField::new(0).get((wpos + dir).with_z(alt as i32)) % 2);
1817 let hide_color =
1818 match RandomField::new(0).get((wpos + dir).with_z(alt as i32)) % 4 {
1819 0 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(73, 29, 0))),
1820 1 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(78, 67, 43))),
1821 2 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(83, 74, 41))),
1822 _ => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(14, 36, 34))),
1823 };
1824 painter
1825 .sphere_with_radius(
1826 (wpos + (2 * dir)).with_z((alt as i32) + 2),
1827 hide_size as f32,
1828 )
1829 .fill(hide_color.clone());
1830 }
1831 painter
1833 .sphere_with_radius(wpos.with_z((alt as i32) + 2), 6.0)
1834 .intersect(painter.aabb(Aabb {
1835 min: (wpos - 6).with_z(alt as i32),
1836 max: (wpos + 6).with_z(alt as i32 + 2 * hut_radius),
1837 }))
1838 .clear();
1839 painter
1841 .aabb(Aabb {
1842 min: Vec2::new(wpos.x - 1, wpos.y - hut_radius - 6).with_z(alt as i32),
1843 max: Vec2::new(wpos.x + 1, wpos.y + hut_radius + 6)
1844 .with_z((alt as i32) + 3),
1845 })
1846 .clear();
1847
1848 for h in 0..(hut_radius + 4) {
1850 painter
1851 .line(
1852 (wpos - hut_radius).with_z((alt as i32) + h),
1853 (wpos + hut_radius).with_z((alt as i32) + h),
1854 0.5,
1855 )
1856 .intersect(
1857 painter.sphere_with_radius(wpos.with_z((alt as i32) + 2), 9.0),
1858 )
1859 .fill(bone_fill.clone());
1860
1861 painter
1862 .line(
1863 Vec2::new(wpos.x - hut_radius, wpos.y + hut_radius)
1864 .with_z((alt as i32) + h),
1865 Vec2::new(wpos.x + hut_radius, wpos.y - hut_radius)
1866 .with_z((alt as i32) + h),
1867 0.5,
1868 )
1869 .intersect(
1870 painter.sphere_with_radius(wpos.with_z((alt as i32) + 2), 9.0),
1871 )
1872 .fill(bone_fill.clone());
1873 }
1874 painter
1875 .sphere_with_radius(wpos.with_z((alt as i32) + 2), 5.0)
1876 .intersect(painter.aabb(Aabb {
1877 min: (wpos - 5).with_z(alt as i32),
1878 max: (wpos + 5).with_z((alt as i32) + 2 * hut_radius),
1879 }))
1880 .clear();
1881
1882 painter.rotated_sprite(
1884 Vec2::new(wpos.x - hut_radius + 1, wpos.y + hut_radius - 2)
1885 .with_z((alt as i32) + 3),
1886 SpriteKind::WallSconce,
1887 0_u8,
1888 );
1889 painter.sprite(wpos.with_z(alt as i32), SpriteKind::FireBowlGround);
1891 let bonehut_mobs =
1892 1 + (RandomField::new(0).get(wpos.with_z(alt as i32)) % 2) as i32;
1893 for _ in 0..bonehut_mobs {
1894 let bonehut_mob_spawn = wpos.with_z(alt as i32);
1895 painter.spawn(random_adlet(bonehut_mob_spawn.as_(), &mut rng));
1896 }
1897 let chest = Fill::Sampling(Arc::new(|wpos| {
1899 Some(match (RandomField::new(0).get(wpos)) % 2 {
1900 0 => Block::air(SpriteKind::DungeonChest1),
1901 _ => Block::air(SpriteKind::Empty),
1902 })
1903 }));
1904 painter
1905 .aabb(Aabb {
1906 min: (wpos - 3).with_z(alt as i32),
1907 max: (wpos - 2).with_z((alt as i32) + 1),
1908 })
1909 .fill(chest);
1910 },
1911 AdletStructure::BossBoneHut => {
1912 let bosshut_pos = wpos;
1913 let hut_radius = 10;
1914 for dir in CARDINALS {
1916 let hide_size = 10
1917 + (RandomField::new(0).get((bosshut_pos + dir).with_z(alt as i32)) % 4);
1918 let hide_color = match RandomField::new(0)
1919 .get((bosshut_pos + dir).with_z(alt as i32))
1920 % 4
1921 {
1922 0 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(73, 29, 0))),
1923 1 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(78, 67, 43))),
1924 2 => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(83, 74, 41))),
1925 _ => Fill::Block(Block::new(BlockKind::Wood, Rgb::new(14, 36, 34))),
1926 };
1927 painter
1928 .sphere_with_radius(
1929 (bosshut_pos + (3 * dir)).with_z((alt as i32) + 2),
1930 hide_size as f32,
1931 )
1932 .fill(hide_color.clone());
1933 }
1934 for h in 0..(hut_radius + 4) {
1936 painter
1937 .line(
1938 (bosshut_pos - hut_radius + 1).with_z((alt as i32) + h),
1939 (bosshut_pos + hut_radius - 1).with_z((alt as i32) + h),
1940 1.5,
1941 )
1942 .intersect(
1943 painter
1944 .sphere_with_radius(bosshut_pos.with_z((alt as i32) + 2), 14.0),
1945 )
1946 .intersect(
1947 painter.aabb(Aabb {
1948 min: (bosshut_pos - 2 * hut_radius).with_z(alt as i32),
1949 max: (bosshut_pos + 2 * hut_radius)
1950 .with_z((alt as i32) + 2 * hut_radius),
1951 }),
1952 )
1953 .fill(bone_fill.clone());
1954
1955 painter
1956 .line(
1957 Vec2::new(
1958 bosshut_pos.x - hut_radius + 1,
1959 bosshut_pos.y + hut_radius - 2,
1960 )
1961 .with_z((alt as i32) + h),
1962 Vec2::new(
1963 bosshut_pos.x + hut_radius - 1,
1964 bosshut_pos.y - hut_radius + 2,
1965 )
1966 .with_z((alt as i32) + h),
1967 1.5,
1968 )
1969 .intersect(
1970 painter
1971 .sphere_with_radius(bosshut_pos.with_z((alt as i32) + 2), 14.0),
1972 )
1973 .intersect(
1974 painter.aabb(Aabb {
1975 min: (bosshut_pos - 2 * hut_radius).with_z(alt as i32),
1976 max: (bosshut_pos + 2 * hut_radius)
1977 .with_z((alt as i32) + 2 * hut_radius),
1978 }),
1979 )
1980 .fill(bone_fill.clone());
1981 }
1982 painter
1983 .sphere_with_radius(bosshut_pos.with_z((alt as i32) + 2), 9.0)
1984 .intersect(painter.aabb(Aabb {
1985 min: (bosshut_pos - 9).with_z(alt as i32),
1986 max: (bosshut_pos + 9).with_z(alt as i32 + 11),
1987 }))
1988 .clear();
1989
1990 for n in 0..2 {
1991 painter
1994 .sphere_with_radius(
1995 (Vec2::new(
1996 bosshut_pos.x,
1997 bosshut_pos.y - hut_radius + (2 * (hut_radius * n)),
1998 ))
1999 .with_z((alt as i32) + 2),
2000 7.0,
2001 )
2002 .intersect(
2003 painter.aabb(Aabb {
2004 min: Vec2::new(
2005 bosshut_pos.x - 7,
2006 bosshut_pos.y - hut_radius + (2 * (hut_radius * n) - 7),
2007 )
2008 .with_z(alt as i32),
2009 max: Vec2::new(
2010 bosshut_pos.x + 7,
2011 bosshut_pos.y - hut_radius + (2 * (hut_radius * n) + 7),
2012 )
2013 .with_z(alt as i32 + 9),
2014 }),
2015 )
2016 .clear();
2017 let entry_start = Vec2::new(
2018 bosshut_pos.x - hut_radius + 3,
2019 bosshut_pos.y - hut_radius - 2 + (n * ((2 * hut_radius) + 4)),
2020 )
2021 .with_z(alt as i32);
2022 let entry_peak = Vec2::new(
2023 bosshut_pos.x,
2024 bosshut_pos.y - hut_radius + (n * (2 * hut_radius)),
2025 )
2026 .with_z(alt as i32 + hut_radius + 2);
2027 let entry_end = Vec2::new(
2028 bosshut_pos.x + hut_radius - 3,
2029 bosshut_pos.y - hut_radius - 2 + (n * ((2 * hut_radius) + 4)),
2030 )
2031 .with_z(alt as i32);
2032 painter
2033 .cubic_bezier(entry_start, entry_peak, entry_peak, entry_end, 1.0)
2034 .fill(bone_fill.clone());
2035 }
2036
2037 painter
2039 .aabb(Aabb {
2040 min: bosshut_pos.with_z((alt as i32) + hut_radius + 5),
2041 max: (bosshut_pos + 1).with_z((alt as i32) + hut_radius + 8),
2042 })
2043 .fill(bone_fill.clone());
2044 painter
2045 .aabb(Aabb {
2046 min: Vec2::new(bosshut_pos.x, bosshut_pos.y - 1)
2047 .with_z((alt as i32) + hut_radius + 8),
2048 max: Vec2::new(bosshut_pos.x + 1, bosshut_pos.y + 2)
2049 .with_z((alt as i32) + hut_radius + 9),
2050 })
2051 .fill(bone_fill.clone());
2052 painter
2053 .aabb(Aabb {
2054 min: bosshut_pos.with_z((alt as i32) + hut_radius + 8),
2055 max: (bosshut_pos + 1).with_z((alt as i32) + hut_radius + 9),
2056 })
2057 .clear();
2058
2059 let top_color = Fill::Sampling(Arc::new(|bosshut_pos| {
2060 Some(match (RandomField::new(0).get(bosshut_pos)) % 10 {
2061 0 => Block::new(BlockKind::Wood, Rgb::new(73, 29, 0)),
2062 1 => Block::new(BlockKind::Wood, Rgb::new(78, 67, 43)),
2063 2 => Block::new(BlockKind::Wood, Rgb::new(83, 74, 41)),
2064 3 => Block::new(BlockKind::Wood, Rgb::new(14, 36, 34)),
2065 _ => Block::new(BlockKind::Rock, Rgb::new(200, 160, 140)),
2066 })
2067 }));
2068 painter
2069 .aabb(Aabb {
2070 min: (bosshut_pos - 1).with_z((alt as i32) + hut_radius + 5),
2071 max: (bosshut_pos + 2).with_z((alt as i32) + hut_radius + 6),
2072 })
2073 .fill(top_color);
2074 for dir in SQUARE_4 {
2076 let corner_pos = Vec2::new(
2077 bosshut_pos.x - (hut_radius / 2) - 1,
2078 bosshut_pos.y - (hut_radius / 2) - 2,
2079 );
2080 let sprite_pos = Vec2::new(
2081 corner_pos.x + dir.x * (hut_radius + 1),
2082 corner_pos.y + dir.y * (hut_radius + 3),
2083 )
2084 .with_z(alt as i32 + 3);
2085 painter.rotated_sprite(
2086 sprite_pos,
2087 SpriteKind::WallSconce,
2088 (2 + (4 * dir.x)) as u8,
2089 );
2090 }
2091 let boss_spawn = wpos.with_z(alt as i32);
2092 painter.spawn(adlet_elder(boss_spawn.as_(), &mut rng));
2093 },
2094 }
2095 }
2096 }
2097}
2098
2099struct RibCageGenerator {
2100 dir: Dir,
2101 length: u32,
2102 spine_height: f32,
2103 spine_radius: f32,
2104 spine_curve_function: fn(f32, f32, f32) -> f32,
2107 spine_curve_amplitude: f32,
2108 spine_curve_wavelength: f32,
2110 spine_start_z_offset: f32,
2111 spine_ctrl0_z_offset: f32,
2112 spine_ctrl1_z_offset: f32,
2113 spine_end_z_offset: f32,
2114 spine_ctrl0_length_fraction: f32,
2115 spine_ctrl1_length_fraction: f32,
2116 rib_base_alt: f32,
2117 rib_spacing: usize,
2118 rib_radius: f32,
2119 rib_run: f32,
2120 rib_ctrl0_run_fraction: f32,
2121 rib_ctrl1_run_fraction: f32,
2122 rib_ctrl0_width_offset: f32,
2123 rib_ctrl1_width_offset: f32,
2124 rib_width_curve: fn(f32) -> f32,
2127 rib_ctrl0_height_fraction: f32,
2128 rib_ctrl1_height_fraction: f32,
2129 vertebra_radius: f32,
2130 vertebra_width: f32,
2131 vertebra_z_offset: f32,
2132}
2133
2134impl RibCageGenerator {
2135 fn bones<'a>(&self, origin: Vec2<i32>, painter: &'a Painter) -> Vec<PrimitiveRef<'a>> {
2136 let RibCageGenerator {
2137 dir,
2138 length,
2139 spine_height,
2140 spine_radius,
2141 spine_curve_function,
2142 spine_curve_amplitude,
2143 spine_curve_wavelength,
2144 spine_start_z_offset,
2145 spine_ctrl0_z_offset,
2146 spine_ctrl1_z_offset,
2147 spine_end_z_offset,
2148 spine_ctrl0_length_fraction,
2149 spine_ctrl1_length_fraction,
2150 rib_base_alt,
2151 rib_spacing,
2152 rib_radius,
2153 rib_run,
2154 rib_ctrl0_run_fraction,
2155 rib_ctrl1_run_fraction,
2156 rib_ctrl0_width_offset,
2157 rib_ctrl1_width_offset,
2158 rib_width_curve,
2159 rib_ctrl0_height_fraction,
2160 rib_ctrl1_height_fraction,
2161 vertebra_radius,
2162 vertebra_width,
2163 vertebra_z_offset,
2164 } = self;
2165 let length_f32 = *length as f32;
2166
2167 let mut bones = Vec::new();
2168
2169 let spine_start = origin
2170 .map(|e| e as f32)
2171 .with_z(spine_height + spine_start_z_offset)
2172 + spine_curve_function(0.0, *spine_curve_amplitude, *spine_curve_wavelength)
2173 * Vec3::unit_y();
2174 let spine_ctrl0 = origin
2175 .map(|e| e as f32)
2176 .with_z(spine_height + spine_ctrl0_z_offset)
2177 + length_f32 * spine_ctrl0_length_fraction * Vec3::unit_x()
2178 + spine_curve_function(
2179 length_f32 * spine_ctrl0_length_fraction,
2180 *spine_curve_amplitude,
2181 *spine_curve_wavelength,
2182 ) * Vec3::unit_y();
2183 let spine_ctrl1 = origin
2184 .map(|e| e as f32)
2185 .with_z(spine_height + spine_ctrl1_z_offset)
2186 + length_f32 * spine_ctrl1_length_fraction * Vec3::unit_x()
2187 + spine_curve_function(
2188 length_f32 * spine_ctrl1_length_fraction,
2189 *spine_curve_amplitude,
2190 *spine_curve_wavelength,
2191 ) * Vec3::unit_y();
2192 let spine_end = origin
2193 .map(|e| e as f32)
2194 .with_z(spine_height + spine_end_z_offset)
2195 + length_f32 * Vec3::unit_x()
2196 + spine_curve_function(length_f32, *spine_curve_amplitude, *spine_curve_wavelength)
2197 * Vec3::unit_y();
2198 let spine_bezier = CubicBezier3 {
2199 start: spine_start,
2200 ctrl0: spine_ctrl0,
2201 ctrl1: spine_ctrl1,
2202 end: spine_end,
2203 };
2204 let spine = painter.cubic_bezier(
2205 spine_start,
2206 spine_ctrl0,
2207 spine_ctrl1,
2208 spine_end,
2209 *spine_radius,
2210 );
2211
2212 let rotation_origin = Vec3::new(spine_start.x, spine_start.y + 0.5, spine_start.z);
2213 let rotate = |prim: PrimitiveRef<'a>, dir: &Dir| -> PrimitiveRef<'a> {
2214 match dir {
2215 Dir::X => prim,
2216 Dir::Y => prim.rotate_about(Mat3::rotation_z(0.5 * PI).as_(), rotation_origin),
2217 Dir::NegX => prim.rotate_about(Mat3::rotation_z(PI).as_(), rotation_origin),
2218 Dir::NegY => prim.rotate_about(Mat3::rotation_z(1.5 * PI).as_(), rotation_origin),
2219 }
2220 };
2221
2222 let spine_rotated = rotate(spine, dir);
2223 bones.push(spine_rotated);
2224
2225 for i in (0..*length).step_by(*rib_spacing) {
2226 enum Side {
2227 Left,
2228 Right,
2229 }
2230
2231 let rib = |side| -> PrimitiveRef {
2232 let y_offset_multiplier = match side {
2233 Side::Left => 1.0,
2234 Side::Right => -1.0,
2235 };
2236 let rib_start: Vec3<f32> = spine_bezier
2237 .evaluate((i as f32 / length_f32).clamped(0.0, 1.0))
2238 + y_offset_multiplier * Vec3::unit_y();
2239 let rib_ctrl0 = Vec3::new(
2240 rib_start.x + rib_ctrl0_run_fraction * rib_run,
2241 rib_start.y
2242 + y_offset_multiplier * rib_ctrl0_width_offset
2243 + y_offset_multiplier * rib_width_curve(i as f32),
2244 rib_base_alt + rib_ctrl0_height_fraction * (rib_start.z - rib_base_alt),
2245 );
2246 let rib_ctrl1 = Vec3::new(
2247 rib_start.x + rib_ctrl1_run_fraction * rib_run,
2248 rib_start.y
2249 + y_offset_multiplier * rib_ctrl1_width_offset
2250 + y_offset_multiplier * rib_width_curve(i as f32),
2251 rib_base_alt + rib_ctrl1_height_fraction * (rib_start.z - rib_base_alt),
2252 );
2253 let rib_end = Vec3::new(
2254 rib_start.x + rib_run,
2255 rib_start.y + y_offset_multiplier * rib_width_curve(i as f32),
2256 *rib_base_alt,
2257 );
2258 painter.cubic_bezier(rib_start, rib_ctrl0, rib_ctrl1, rib_end, *rib_radius)
2259 };
2260 let l_rib = rib(Side::Left);
2261 let l_rib_rotated = rotate(l_rib, dir);
2262 bones.push(l_rib_rotated);
2263
2264 let r_rib = rib(Side::Right);
2265 let r_rib_rotated = rotate(r_rib, dir);
2266 bones.push(r_rib_rotated);
2267
2268 let vertebra_start: Vec3<f32> =
2269 spine_bezier.evaluate((i as f32 / length_f32).clamped(0.0, 1.0)) + Vec3::unit_y();
2270 let vertebra = painter.ellipsoid(Aabb {
2271 min: Vec3::new(
2272 vertebra_start.x - vertebra_width,
2273 vertebra_start.y - 1.0 - vertebra_radius,
2274 vertebra_start.z - vertebra_radius + vertebra_z_offset,
2275 )
2276 .map(|e| e.round() as i32),
2277 max: Vec3::new(
2278 vertebra_start.x + vertebra_width,
2279 vertebra_start.y - 1.0 + vertebra_radius,
2280 vertebra_start.z + vertebra_radius + vertebra_z_offset,
2281 )
2282 .map(|e| e.round() as i32),
2283 });
2284 let vertebra_rotated = rotate(vertebra, dir);
2285 bones.push(vertebra_rotated);
2286 }
2287
2288 bones
2289 }
2290}
2291
2292fn adlet_hunter<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2293 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2294 "common.entity.dungeon.adlet.hunter",
2295 rng,
2296 None,
2297 )
2298}
2299
2300fn adlet_icepicker<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2301 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2302 "common.entity.dungeon.adlet.icepicker",
2303 rng,
2304 None,
2305 )
2306}
2307
2308fn adlet_tracker<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2309 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2310 "common.entity.dungeon.adlet.tracker",
2311 rng,
2312 None,
2313 )
2314}
2315
2316fn random_adlet<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2317 match rng.gen_range(0..3) {
2318 0 => adlet_hunter(pos, rng),
2319 1 => adlet_icepicker(pos, rng),
2320 _ => adlet_tracker(pos, rng),
2321 }
2322}
2323
2324fn adlet_elder<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2325 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2326 "common.entity.dungeon.adlet.elder",
2327 rng,
2328 None,
2329 )
2330}
2331
2332fn rat<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2333 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2334 "common.entity.wild.peaceful.rat",
2335 rng,
2336 None,
2337 )
2338}
2339
2340fn wolf<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2341 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2342 "common.entity.wild.aggressive.wolf",
2343 rng,
2344 None,
2345 )
2346}
2347
2348fn bear<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2349 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2350 "common.entity.wild.aggressive.bear",
2351 rng,
2352 None,
2353 )
2354}
2355
2356fn frostfang<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2357 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2358 "common.entity.wild.aggressive.frostfang",
2359 rng,
2360 None,
2361 )
2362}
2363
2364fn roshwalr<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2365 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2366 "common.entity.wild.aggressive.roshwalr",
2367 rng,
2368 None,
2369 )
2370}
2371
2372fn icedrake<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2373 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2374 "common.entity.wild.aggressive.icedrake",
2375 rng,
2376 None,
2377 )
2378}
2379
2380fn tursus<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2381 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2382 "common.entity.wild.aggressive.tursus",
2383 rng,
2384 None,
2385 )
2386}
2387
2388fn random_yetipit_mob<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2389 match rng.gen_range(0..4) {
2390 0 => frostfang(pos, rng),
2391 1 => roshwalr(pos, rng),
2392 2 => icedrake(pos, rng),
2393 _ => tursus(pos, rng),
2394 }
2395}
2396
2397fn yeti<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
2398 EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
2399 "common.entity.dungeon.adlet.yeti",
2400 rng,
2401 None,
2402 )
2403}
2404
2405#[cfg(test)]
2406mod tests {
2407 use super::*;
2408
2409 #[test]
2410 fn test_creating_entities() {
2411 let pos = Vec3::zero();
2412 let mut rng = thread_rng();
2413
2414 adlet_hunter(pos, &mut rng);
2415 adlet_icepicker(pos, &mut rng);
2416 adlet_tracker(pos, &mut rng);
2417 random_adlet(pos, &mut rng);
2418 adlet_elder(pos, &mut rng);
2419 rat(pos, &mut rng);
2420 wolf(pos, &mut rng);
2421 bear(pos, &mut rng);
2422 frostfang(pos, &mut rng);
2423 roshwalr(pos, &mut rng);
2424 icedrake(pos, &mut rng);
2425 tursus(pos, &mut rng);
2426 }
2427}