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
17pub struct TerracottaHouse {
19 bounds: Aabr<i32>,
21 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 let model_radius = (room_size / 2) + 4;
134 for dir in DIAGONALS {
135 let pos = center + dir * model_radius;
136 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 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 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 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 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 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 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 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 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 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 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 for dir in CARDINALS {
371 let pos = center + dir * ((room_size / 4) + 4);
372 painter.sprite(pos.with_z(base), SpriteKind::TerracottaStatue);
373 }
374 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 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}