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
17pub struct TerracottaYard {
19 bounds: Aabr<i32>,
21 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 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 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 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 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 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 let model_radius = (room_size / 2) + 7;
192 for dir in NEIGHBORS {
193 let pos = center + dir * model_radius;
194 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 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 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 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}