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