veloren_world/site2/plot/
terracotta_house.rs

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