Skip to main content

veloren_world/site/plot/
terracotta_house.rs

1use super::*;
2use crate::{
3    Land,
4    assets::AssetHandle,
5    site::generation::PrimitiveTransform,
6    util::{DIAGONALS, NEIGHBORS, RandomField, Sampler, within_distance},
7};
8use common::{
9    generation::EntityInfo,
10    terrain::{BlockKind, SpriteKind, Structure as PrefabStructure, StructuresGroup},
11};
12use lazy_static::lazy_static;
13use rand::prelude::*;
14use std::{f32::consts::TAU, sync::Arc};
15use vek::*;
16
17/// Represents house data generated by the `generate()` method
18pub struct TerracottaHouse {
19    /// Axis aligned bounding region for the house
20    bounds: Aabr<i32>,
21    /// Approximate altitude of the door tile
22    pub(crate) alt: i32,
23}
24
25impl TerracottaHouse {
26    pub fn generate(
27        land: &Land,
28        _rng: &mut impl Rng,
29        site: &Site,
30        tile_aabr: Aabr<i32>,
31        alt: Option<i32>,
32    ) -> Self {
33        let bounds = Aabr {
34            min: site.tile_wpos(tile_aabr.min),
35            max: site.tile_wpos(tile_aabr.max),
36        };
37        Self {
38            bounds,
39            alt: alt.unwrap_or_else(|| {
40                land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2))
41                    as i32
42                    + 2
43            }),
44        }
45    }
46}
47
48impl Structure for TerracottaHouse {
49    #[cfg(feature = "dyn-lib")]
50    #[unsafe(export_name = "as_dyn_structure_terracottahouse")]
51    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
52        Some((Self::as_dyn_impl(self), "as_dyn_structure_terracottahouse"))
53    }
54
55    fn spawn_rules_inner(
56        &self,
57        spawn_rules: &mut SpawnRules,
58        _land: &Land,
59        wpos: Vec2<i32>,
60        _weight: f32,
61    ) {
62        spawn_rules.trees &= !within_distance(wpos, self.bounds.center(), 85);
63        spawn_rules.waypoints = false;
64
65        const MIN_FLAT_DIST: f32 = 12.0;
66        const SLOPE_LENGTH: f32 = 16.0;
67        let dist = wpos.as_::<f32>().distance(self.bounds.center().as_());
68        let weight = (1.0 - (dist - MIN_FLAT_DIST).max(0.0) / SLOPE_LENGTH).max(0.0);
69        spawn_rules.prefer_alt(self.alt as f32, weight);
70    }
71
72    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
73        let base = self.alt + 3;
74        let center = self.bounds.center();
75        let mut rng = rand::rng();
76        let clay_broken = Fill::Sampling(Arc::new(|center| {
77            Some(match (RandomField::new(0).get(center)) % 42 {
78                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
79                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
80                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
81                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
82                36..=38 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
83                _ => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
84            })
85        }));
86        let clay_unbroken = Fill::Sampling(Arc::new(|center| {
87            Some(match (RandomField::new(0).get(center)) % 40 {
88                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
89                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
90                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
91                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
92                _ => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
93            })
94        }));
95        let grass_fill = Fill::Sampling(Arc::new(|wpos| {
96            Some(match (RandomField::new(0).get(wpos)) % 20 {
97                1..=2 => Block::air(SpriteKind::JungleRedGrass),
98                3..=7 => Block::air(SpriteKind::JungleLeafyPlant),
99                8 => Block::air(SpriteKind::JungleFern),
100                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
101            })
102        }));
103        let roof_color = Fill::Sampling(Arc::new(|center| {
104            Some(match (RandomField::new(0).get(center)) % 400 {
105                0..=4 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
106                5..=9 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
107                10..=14 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
108                15..=19 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
109                20..=21 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
110                22..=23 => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
111                _ => Block::new(BlockKind::GlowingRock, Rgb::new(73, 53, 42)),
112            })
113        }));
114        let sand = Fill::Brick(BlockKind::Misc, Rgb::new(235, 178, 99), 12);
115        let size = 30;
116        let room_size = 15 * (size / 10);
117        let roof_size = 16 * (size / 10);
118        let carve_size = 14 * (size / 10) + 2;
119        let roof_height = room_size / 3;
120        let storeys = 5;
121        let var = size / 5;
122        let clear_var = var / 2;
123        let clear_limit = painter.aabb(Aabb {
124            min: (center - (room_size / 2) - 2).with_z(base),
125            max: (center + (room_size / 2) + 2).with_z(base + (2 * room_size)),
126        });
127        let clear_limit_down = painter.aabb(Aabb {
128            min: (center - (room_size / 2) - 5).with_z(base - (2 * room_size)),
129            max: (center + (room_size / 2) + 5).with_z(base),
130        });
131        let decay = RandomField::new(0).get(center.with_z(base)) % 2;
132        // models
133        let model_radius = (room_size / 2) + 4;
134        for dir in DIAGONALS {
135            let pos = center + dir * model_radius;
136            // foundation
137            painter
138                .cylinder(Aabb {
139                    min: (pos - 10).with_z(base - room_size),
140                    max: (pos + 10).with_z(base - 3),
141                })
142                .fill(clay_unbroken.clone());
143            painter
144                .cylinder(Aabb {
145                    min: (pos - 10).with_z(base - 4),
146                    max: (pos + 10).with_z(base - 3),
147                })
148                .fill(clay_broken.clone());
149            painter
150                .cylinder(Aabb {
151                    min: (pos - 9).with_z(base - 4),
152                    max: (pos + 9).with_z(base - 3),
153                })
154                .fill(sand.clone());
155            // jungle sprites
156            painter
157                .cylinder(Aabb {
158                    min: (pos - 7).with_z(base - 3),
159                    max: (pos + 7).with_z(base - 2),
160                })
161                .fill(grass_fill.clone());
162            // models
163            let model_pos = pos.with_z(base - 5);
164            match RandomField::new(0).get(model_pos) % 2 {
165                0 => {
166                    lazy_static! {
167                        pub static ref MODEL: AssetHandle<StructuresGroup> =
168                            PrefabStructure::load_group(
169                                "site_structures.terracotta.terracotta_decor_small"
170                            );
171                    }
172                    let rng = RandomField::new(0).get(model_pos) % 62;
173                    let model = MODEL.read();
174                    let model = model[rng as usize % model.len()].clone();
175                    painter
176                        .prim(Primitive::Prefab(Box::new(model.clone())))
177                        .translate(model_pos)
178                        .fill(Fill::Prefab(Box::new(model), model_pos, rng));
179                },
180
181                _ => {
182                    lazy_static! {
183                        pub static ref MODEL: AssetHandle<StructuresGroup> =
184                            PrefabStructure::load_group("trees.palms");
185                    }
186                    let rng = RandomField::new(0).get(model_pos) % 62;
187                    let model = MODEL.read();
188                    let model = model[rng as usize % model.len()].clone();
189                    painter
190                        .prim(Primitive::Prefab(Box::new(model.clone())))
191                        .translate(model_pos)
192                        .fill(Fill::Prefab(Box::new(model), model_pos, rng));
193                },
194            }
195        }
196        // foundation
197        painter
198            .superquadric(
199                Aabb {
200                    min: (center - (room_size / 2) - 10).with_z(base - room_size - 15),
201                    max: (center + (room_size / 2) + 10).with_z(base + 5),
202                },
203                2.5,
204            )
205            .fill(clay_unbroken.clone());
206        // base room
207        painter
208            .superquadric(
209                Aabb {
210                    min: (center - (room_size / 2)).with_z(base - (room_size / 2)),
211                    max: (center + (room_size / 2)).with_z(base + (room_size / 2)),
212                },
213                2.5,
214            )
215            .fill(clay_broken.clone());
216        // solid bottom
217        painter
218            .superquadric(
219                Aabb {
220                    min: (center - (room_size / 2)).with_z(base - (room_size / 2)),
221                    max: (center + (room_size / 2)).with_z(base + (room_size / 2)),
222                },
223                2.5,
224            )
225            .intersect(clear_limit_down)
226            .fill(clay_unbroken.clone());
227        // roof and top rooms
228        for s in 0..storeys {
229            painter
230                .superquadric(
231                    Aabb {
232                        min: (center - (roof_size / 2) + (s * var)).with_z(
233                            base + roof_height - (size / 4) + (s * (roof_size / 4)) + (s * var),
234                        ),
235                        max: (center + (roof_size / 2) - (s * var)).with_z(
236                            base + roof_height - (size / 4) + roof_size + (s * (roof_size / 4))
237                                - (s * var),
238                        ),
239                    },
240                    2.5,
241                )
242                .fill(clay_broken.clone());
243            painter
244                .superquadric(
245                    Aabb {
246                        min: (center - (roof_size / 2) + (s * var) + 1).with_z(
247                            base + roof_height - (size / 4) + (s * (roof_size / 4)) + (s * var) + 1,
248                        ),
249                        max: (center + (roof_size / 2) - (s * var) - 1).with_z(
250                            base + roof_height - (size / 4) + roof_size + (s * (roof_size / 4))
251                                - (s * var)
252                                - 1,
253                        ),
254                    },
255                    2.5,
256                )
257                .fill(roof_color.clone());
258            for dir in CARDINALS {
259                let pos = center + dir * (size - (s * var));
260
261                painter
262                    .superquadric(
263                        Aabb {
264                            min: (pos - (carve_size / 2) + (s * clear_var)).with_z(
265                                base + roof_height + (s * (roof_size / 4)) + (s * clear_var),
266                            ),
267                            max: (pos + (carve_size / 2) - (s * clear_var)).with_z(
268                                base + roof_height + carve_size + (s * (roof_size / 4))
269                                    - (s * clear_var),
270                            ),
271                        },
272                        2.5,
273                    )
274                    .intersect(clear_limit)
275                    .clear();
276            }
277        }
278        // clear base room & entries
279        painter
280            .superquadric(
281                Aabb {
282                    min: (center - (room_size / 2) + 5).with_z(base - (room_size / 2) + 5),
283                    max: (center + (room_size / 2) - 5).with_z(base + (room_size / 2) - 5),
284                },
285                2.5,
286            )
287            .union(
288                painter
289                    .superquadric(
290                        Aabb {
291                            min: Vec2::new(
292                                center.x - (room_size / 4),
293                                center.y - (3 * room_size / 4),
294                            )
295                            .with_z(base - (room_size / 4)),
296                            max: Vec2::new(
297                                center.x + (room_size / 4),
298                                center.y + (3 * room_size / 4),
299                            )
300                            .with_z(base + (room_size / 4)),
301                        },
302                        2.5,
303                    )
304                    .union(
305                        painter.superquadric(
306                            Aabb {
307                                min: Vec2::new(
308                                    center.x - (3 * room_size / 4),
309                                    center.y - (room_size / 4),
310                                )
311                                .with_z(base - (room_size / 4)),
312                                max: Vec2::new(
313                                    center.x + (3 * room_size / 4),
314                                    center.y + (room_size / 4),
315                                )
316                                .with_z(base + (room_size / 4)),
317                            },
318                            2.5,
319                        ),
320                    ),
321            )
322            .intersect(clear_limit)
323            .clear();
324        // clear top
325        painter
326            .superquadric(
327                Aabb {
328                    min: (center - (room_size / 3))
329                        .with_z(base + (3 * (room_size / 10)) - (room_size / 3)),
330                    max: (center + (room_size / 3))
331                        .with_z(base + (3 * (room_size / 10)) + (room_size / 4)),
332                },
333                2.5,
334            )
335            .clear();
336        painter
337            .superquadric(
338                Aabb {
339                    min: (center - (room_size / 4))
340                        .with_z(base + (3 * (room_size / 10)) - (room_size / 4)),
341                    max: (center + (room_size / 4))
342                        .with_z(base + (3 * (room_size / 10)) + (room_size / 3)),
343                },
344                2.5,
345            )
346            .clear();
347        // solid floor
348        painter
349            .cylinder(Aabb {
350                min: (center - (room_size / 2) + 1).with_z(base - 1),
351                max: (center + (room_size / 2) - 1).with_z(base),
352            })
353            .fill(clay_unbroken.clone());
354
355        // npcs
356        // guards
357        let radius_guards = (room_size / 4) + 4;
358        let guards = 4.0_f32;
359        let phi_guards = TAU / guards;
360        for n in 1..=guards as i32 {
361            let pos = Vec2::new(
362                center.x + (radius_guards as f32 * ((n as f32 * phi_guards).cos())) as i32,
363                center.y + (radius_guards as f32 * ((n as f32 * phi_guards).sin())) as i32,
364            )
365            .with_z(base);
366            terracotta_palace::spawn_random_entity(pos, painter, 1..=1);
367        }
368        if decay == 0 {
369            // statues
370            for dir in CARDINALS {
371                let pos = center + dir * ((room_size / 4) + 4);
372                painter.sprite(pos.with_z(base), SpriteKind::TerracottaStatue);
373            }
374            // iron spike trap
375            painter
376                .cylinder(Aabb {
377                    min: (center - (room_size / 6)).with_z(base - 12),
378                    max: (center + (room_size / 6)).with_z(base),
379                })
380                .clear();
381            painter
382                .cylinder(Aabb {
383                    min: (center - (room_size / 6)).with_z(base - 13),
384                    max: (center + (room_size / 6)).with_z(base - 12),
385                })
386                .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
387            painter
388                .cylinder(Aabb {
389                    min: (center - (room_size / 6)).with_z(base - 1),
390                    max: (center + (room_size / 6)).with_z(base),
391                })
392                .fill(Fill::Block(Block::air(SpriteKind::TerracottaBlock)));
393            painter
394                .cylinder(Aabb {
395                    min: (center).with_z(base - 14),
396                    max: (center + 1).with_z(base),
397                })
398                .fill(clay_unbroken);
399            // miniboss
400            painter.spawn(EntityInfo::at(center.with_z(base).as_()).with_asset_expect(
401                "common.entity.dungeon.terracotta.terracotta_statue_key_chance",
402                &mut rng,
403                None,
404            ));
405        } else {
406            let decay_limiter = painter.aabb(Aabb {
407                min: (center - (room_size / 2)).with_z(base + 4),
408                max: (center + (room_size / 2)).with_z(base + (5 * roof_height)),
409            });
410            for dir in NEIGHBORS {
411                let carve_pos = center + dir * room_size;
412                let decay_var = RandomField::new(0).get(carve_pos.with_z(base)) % 15;
413                painter
414                    .line(
415                        carve_pos.with_z(base),
416                        center.with_z(base + (5 * roof_height)),
417                        10.0 + decay_var as f32,
418                    )
419                    .intersect(decay_limiter)
420                    .clear();
421            }
422        }
423    }
424}