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