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