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(
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 let model_radius = (room_size / 2) + 4;
126 for dir in DIAGONALS {
127 let pos = center + dir * model_radius;
128 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 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 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 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 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 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 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 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 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 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 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 for dir in CARDINALS {
363 let pos = center + dir * ((room_size / 4) + 4);
364 painter.sprite(pos.with_z(base), SpriteKind::TerracottaStatue);
365 }
366 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 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}