Skip to main content

veloren_world/site/plot/
terracotta_yard.rs

1use super::*;
2use crate::{
3    Land,
4    assets::AssetHandle,
5    site::generation::PrimitiveTransform,
6    util::{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 TerracottaYard {
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 TerracottaYard {
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 TerracottaYard {
49    #[cfg(feature = "dyn-lib")]
50    #[unsafe(export_name = "as_dyn_structure_terracottayard")]
51    fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
52        Some((Self::as_dyn_impl(self), "as_dyn_structure_terracottayard"))
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
66    fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
67        let base = self.alt + 4;
68        let center = self.bounds.center();
69        let mut rng = rand::rng();
70        let clay_unbroken = Fill::Sampling(Arc::new(|center| {
71            Some(match (RandomField::new(0).get(center)) % 40 {
72                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
73                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
74                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
75                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
76                _ => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
77            })
78        }));
79        let clay_broken = Fill::Sampling(Arc::new(|center| {
80            Some(match (RandomField::new(0).get(center)) % 42 {
81                0..=8 => Block::new(BlockKind::Rock, Rgb::new(242, 161, 53)),
82                9..=17 => Block::new(BlockKind::Rock, Rgb::new(253, 199, 81)),
83                18..=26 => Block::new(BlockKind::Rock, Rgb::new(254, 210, 91)),
84                27..=35 => Block::new(BlockKind::Rock, Rgb::new(254, 216, 101)),
85                36..=38 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
86                _ => Block::new(BlockKind::Rock, Rgb::new(250, 185, 71)),
87            })
88        }));
89        let grass_fill = Fill::Sampling(Arc::new(|wpos| {
90            Some(match (RandomField::new(0).get(wpos)) % 20 {
91                1..=2 => Block::air(SpriteKind::JungleRedGrass),
92                3..=7 => Block::air(SpriteKind::JungleLeafyPlant),
93                8 => Block::air(SpriteKind::JungleFern),
94                _ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
95            })
96        }));
97        let water_fill = Fill::Block(Block::new(BlockKind::Water, Rgb::zero()));
98        let sand = Fill::Brick(BlockKind::Misc, Rgb::new(235, 178, 99), 12);
99        let size = 30;
100        let room_size = 15 * (size / 10);
101        let water = RandomField::new(0).get(center.with_z(base)) % 2;
102        // foundation
103        painter
104            .superquadric(
105                Aabb {
106                    min: (center - (room_size / 2) - 10).with_z(base - room_size - 15),
107                    max: (center + (room_size / 2) + 10).with_z(base + 5),
108                },
109                2.5,
110            )
111            .fill(clay_unbroken.clone());
112        // clear floor
113        let clear_limit = painter.aabb(Aabb {
114            min: (center - (room_size / 2) - 5).with_z(base),
115            max: (center + (room_size / 2) + 5).with_z(base + room_size),
116        });
117        painter
118            .superquadric(
119                Aabb {
120                    min: (center - (room_size / 2) - 5).with_z(base - (room_size / 2) - 5),
121                    max: (center + (room_size / 2) + 5).with_z(base + room_size),
122                },
123                2.5,
124            )
125            .intersect(clear_limit)
126            .clear();
127        if water < 1 {
128            // iron spike trap
129            painter
130                .cylinder(Aabb {
131                    min: (center - (room_size / 4)).with_z(base - 12),
132                    max: (center + (room_size / 4)).with_z(base),
133                })
134                .clear();
135            painter
136                .cylinder(Aabb {
137                    min: (center - (room_size / 4)).with_z(base - 13),
138                    max: (center + (room_size / 4)).with_z(base - 12),
139                })
140                .fill(Fill::Block(Block::air(SpriteKind::IronSpike)));
141            painter
142                .cylinder(Aabb {
143                    min: (center - (room_size / 4)).with_z(base - 1),
144                    max: (center + (room_size / 4)).with_z(base),
145                })
146                .fill(Fill::Block(Block::air(SpriteKind::TerracottaBlock)));
147            painter
148                .cylinder(Aabb {
149                    min: (center).with_z(base - 14),
150                    max: (center + 1).with_z(base),
151                })
152                .fill(clay_unbroken);
153            // sprites
154            let radius_lamps_main = (room_size / 4) + 6;
155            let lamps_main = 8.0_f32;
156            let phi_lamps_main = TAU / lamps_main;
157            for n in 1..=lamps_main as i32 {
158                let pos = Vec2::new(
159                    center.x
160                        + (radius_lamps_main as f32 * ((n as f32 * phi_lamps_main).cos())) as i32,
161                    center.y
162                        + (radius_lamps_main as f32 * ((n as f32 * phi_lamps_main).sin())) as i32,
163                );
164                painter.sprite(pos.with_z(base), SpriteKind::TerracottaStatue);
165            }
166            // npcs
167            // statue
168            painter.spawn(
169                EntityInfo::at((center).with_z(base).as_()).with_asset_expect(
170                    "common.entity.dungeon.terracotta.terracotta_statue_key",
171                    &mut rng,
172                    None,
173                ),
174            );
175        } else {
176            let basin = painter.superquadric(
177                Aabb {
178                    min: (center - (room_size / 4) - 12).with_z(base - 12),
179                    max: (center + (room_size / 4) + 12).with_z(base + 12),
180                },
181                2.5,
182            );
183            basin.clear();
184            let water_level = (RandomField::new(0).get(center.with_z(base)) % 8) as i32;
185            let water_limit = painter.aabb(Aabb {
186                min: (center - (room_size / 4) - 12).with_z(base - 12),
187                max: (center + (room_size / 4) + 12).with_z(base - 4 - water_level),
188            });
189            basin.intersect(water_limit).fill(water_fill);
190            // models
191            let model_radius = (room_size / 2) + 7;
192            for dir in NEIGHBORS {
193                let pos = center + dir * model_radius;
194                // foundation
195                painter
196                    .cylinder(Aabb {
197                        min: (pos - 10).with_z(base - room_size),
198                        max: (pos + 10).with_z(base - 3),
199                    })
200                    .fill(clay_unbroken.clone());
201                painter
202                    .cylinder(Aabb {
203                        min: (pos - 10).with_z(base - 4),
204                        max: (pos + 10).with_z(base - 3),
205                    })
206                    .fill(clay_broken.clone());
207                painter
208                    .cylinder(Aabb {
209                        min: (pos - 9).with_z(base - 4),
210                        max: (pos + 9).with_z(base - 3),
211                    })
212                    .fill(sand.clone());
213                // jungle sprites
214                painter
215                    .cylinder(Aabb {
216                        min: (pos - 7).with_z(base - 3),
217                        max: (pos + 7).with_z(base - 2),
218                    })
219                    .fill(grass_fill.clone());
220                // models
221                let model_pos = pos.with_z(base - 5);
222                match RandomField::new(0).get(model_pos) % 2 {
223                    0 => {
224                        lazy_static! {
225                            pub static ref MODEL: AssetHandle<StructuresGroup> =
226                                PrefabStructure::load_group(
227                                    "site_structures.terracotta.terracotta_decor_small"
228                                );
229                        }
230                        let rng = RandomField::new(0).get(model_pos) % 62;
231                        let model = MODEL.read();
232                        let model = model[rng as usize % model.len()].clone();
233                        painter
234                            .prim(Primitive::Prefab(Box::new(model.clone())))
235                            .translate(model_pos)
236                            .fill(Fill::Prefab(Box::new(model), model_pos, rng));
237                    },
238
239                    _ => {
240                        lazy_static! {
241                            pub static ref MODEL: AssetHandle<StructuresGroup> =
242                                PrefabStructure::load_group("trees.palms");
243                        }
244                        let rng = RandomField::new(0).get(model_pos) % 62;
245                        let model = MODEL.read();
246                        let model = model[rng as usize % model.len()].clone();
247                        painter
248                            .prim(Primitive::Prefab(Box::new(model.clone())))
249                            .translate(model_pos)
250                            .fill(Fill::Prefab(Box::new(model), model_pos, rng));
251                    },
252                }
253            }
254        }
255        // guards
256        let radius_guards = (room_size / 4) + 12;
257        let guards = 4.0_f32;
258        let phi_guards = TAU / guards;
259        for n in 1..=guards as i32 {
260            let pos = Vec2::new(
261                center.x + (radius_guards as f32 * ((n as f32 * phi_guards).cos())) as i32,
262                center.y + (radius_guards as f32 * ((n as f32 * phi_guards).sin())) as i32,
263            )
264            .with_z(base);
265            terracotta_palace::spawn_random_entity(pos, painter, 1..=1);
266        }
267    }
268}