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
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 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 let model_radius = (room_size / 2) + 4;
123 for dir in DIAGONALS {
124 let pos = center + dir * model_radius;
125 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 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 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 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 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 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 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 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 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 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 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 for dir in CARDINALS {
360 let pos = center + dir * ((room_size / 4) + 4);
361 painter.sprite(pos.with_z(base), SpriteKind::TerracottaStatue);
362 }
363 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 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}