1use super::*;
2use crate::{ColumnSample, Land};
3use common::terrain::{
4 Block, BlockKind, SpriteKind,
5 sprite::{Owned, RelativeNeighborPosition},
6};
7use rand::{prelude::*, seq::IndexedRandom};
8use strum::{EnumIter, IntoEnumIterator};
9use vek::*;
10
11#[derive(EnumIter)]
12enum Crop {
13 Wildflower,
14 Wheat,
15 Flax,
16 Corn,
17 Tomato,
18 Carrot,
19 Radish,
20 Turnip,
21 Lettuce,
22 Pumpkin,
23 Sunflower,
24 Cactus,
25}
26
27impl Crop {
28 fn row_spacing(&self) -> Option<(f32, f32)> {
31 match self {
32 Self::Wildflower => None,
33 Self::Wheat | Self::Flax | Self::Corn => Some((6.0, 0.8)),
35 Self::Tomato | Self::Cactus => Some((3.0, 1.0 / 3.0)),
37 Self::Carrot | Self::Radish | Self::Turnip | Self::Lettuce | Self::Pumpkin => {
39 Some((6.0, 0.75))
40 },
41 Self::Sunflower => Some((4.0, 0.5)),
42 }
43 }
44
45 fn sprites(&self) -> &[(f32, Option<SpriteKind>)] {
46 match self {
47 Self::Wheat => &[
48 (4.0, Some(SpriteKind::Empty)),
49 (1.0, Some(SpriteKind::WheatGreen)),
50 (1.0, Some(SpriteKind::WheatYellow)),
51 ],
52 Self::Flax => &[
53 (4.0, Some(SpriteKind::Empty)),
54 (1.0, Some(SpriteKind::Flax)),
55 ],
56 Self::Corn => &[
57 (4.0, Some(SpriteKind::Empty)),
58 (1.0, Some(SpriteKind::Corn)),
59 ],
60 Self::Wildflower => &[
61 (40.0, None),
62 (1.0, Some(SpriteKind::BlueFlower)),
63 (1.0, Some(SpriteKind::PinkFlower)),
64 (1.0, Some(SpriteKind::PurpleFlower)),
65 (0.1, Some(SpriteKind::RedFlower)),
66 (1.0, Some(SpriteKind::WhiteFlower)),
67 (1.0, Some(SpriteKind::YellowFlower)),
68 (1.0, Some(SpriteKind::Sunflower)),
69 (4.0, Some(SpriteKind::LongGrass)),
70 (4.0, Some(SpriteKind::MediumGrass)),
71 (4.0, Some(SpriteKind::ShortGrass)),
72 ],
73 Self::Tomato => &[
74 (1.5, Some(SpriteKind::Empty)),
75 (1.0, Some(SpriteKind::Tomato)),
76 ],
77 Self::Carrot => &[
78 (5.0, Some(SpriteKind::Empty)),
79 (1.0, Some(SpriteKind::Carrot)),
80 ],
81 Self::Radish => &[
82 (5.0, Some(SpriteKind::Empty)),
83 (1.0, Some(SpriteKind::Radish)),
84 ],
85 Self::Turnip => &[
86 (5.0, Some(SpriteKind::Empty)),
87 (1.0, Some(SpriteKind::Turnip)),
88 ],
89 Self::Lettuce => &[
90 (5.0, Some(SpriteKind::Empty)),
91 (1.0, Some(SpriteKind::Lettuce)),
92 ],
93 Self::Pumpkin => &[
94 (5.0, Some(SpriteKind::Empty)),
95 (1.0, Some(SpriteKind::Pumpkin)),
96 ],
97 Self::Sunflower => &[
98 (5.0, Some(SpriteKind::Empty)),
99 (1.0, Some(SpriteKind::Sunflower)),
100 ],
101 Self::Cactus => &[
102 (10.0, Some(SpriteKind::Empty)),
103 (1.0, Some(SpriteKind::BarrelCactus)),
104 (1.0, Some(SpriteKind::RoundCactus)),
105 (1.0, Some(SpriteKind::ShortCactus)),
106 (1.0, Some(SpriteKind::MedFlatCactus)),
107 (1.0, Some(SpriteKind::ShortFlatCactus)),
108 (1.0, Some(SpriteKind::LargeCactus)),
109 (1.0, Some(SpriteKind::TallCactus)),
110 ],
111 }
112 }
113}
114
115pub struct FarmField {
117 crop: Crop,
118 bounds: Aabr<i32>,
120 pub(crate) alt: i32,
122 ori: Vec2<f32>,
123 is_desert: bool,
124 step_size: Option<f32>,
125}
126
127impl FarmField {
128 pub fn generate(
129 land: &Land,
130 rng: &mut impl Rng,
131 site: &Site,
132 door_tile: Vec2<i32>,
133 door_dir: Vec2<i32>,
134 tile_aabr: Aabr<i32>,
135 is_desert: bool,
136 ) -> Self {
137 let bounds = Aabr {
138 min: site.tile_wpos(tile_aabr.min),
139 max: site.tile_wpos(tile_aabr.max),
140 };
141
142 let crop = if is_desert {
143 Crop::Cactus
144 } else {
145 Crop::iter()
146 .filter(|crop| !matches!(crop, Crop::Cactus))
147 .choose(rng)
148 .unwrap()
149 };
150
151 let center_grad = land.get_gradient_approx(bounds.center());
153 let step_size = if center_grad > 0.25 {
154 Some((center_grad * 8.0).clamped(3.0, 12.0))
155 } else {
156 None
157 };
158
159 Self {
160 bounds,
161 alt: land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32,
162 ori: {
163 let norm = land
164 .get_approx_chunk_terrain_normal(bounds.center())
165 .unwrap_or_default();
166 let ori = rng.random_range(0.0..std::f32::consts::TAU);
167 norm.xy() * 5.0 + Vec2::new(ori.sin(), ori.cos())
168 },
169 crop,
170 is_desert,
171 step_size,
172 }
173 }
174
175 fn step(&self, alt: f32) -> Option<(f32, f32)> {
177 const STEP_POW: i32 = 8;
178
179 if let Some(step_size) = self.step_size {
180 let step_alt = alt / step_size;
181 let step_fract = step_alt.fract();
182 let h_fract = if step_fract < 0.5 {
183 (step_fract * 2.0).powi(STEP_POW) * 0.5
184 } else {
185 1.0 - ((1.0 - step_fract) * 2.0).powi(STEP_POW) * 0.5
186 };
187 Some((step_fract, (step_alt.floor() + h_fract) * step_size))
188 } else {
189 None
190 }
191 }
192}
193
194impl Structure for FarmField {
195 #[cfg(feature = "dyn-lib")]
196 #[unsafe(export_name = "as_dyn_structure_farmfield")]
197 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
198 Some((Self::as_dyn_impl(self), "as_dyn_structure_farmfield"))
199 }
200
201 fn spawn_rules_inner(
202 &self,
203 spawn_rules: &mut SpawnRules,
204 land: &Land,
205 wpos: Vec2<i32>,
206 weight: f32,
207 ) {
208 if let Some((_, step_alt)) = self.step(land.get_alt_approx(wpos)) {
209 spawn_rules.prefer_alt(step_alt, weight);
210 }
211 }
212
213 fn terrain_surface_at_inner(
214 &self,
215 wpos: Vec2<i32>,
216 old: Block,
217 rng: &mut ChaCha8Rng,
218 col: &ColumnSample,
219 z_off: i32,
220 _site: &Site,
221 ) -> Option<Block> {
222 let t = (self.ori.yx() * wpos.as_()).magnitude();
223
224 let is_bank = self
226 .step(col.alt)
227 .is_some_and(|(step_fract, _)| (step_fract - 0.5).abs() < 0.45);
228
229 let is_trench = self
230 .crop
231 .row_spacing()
232 .map(|(w, p)| (t / w).fract() <= p)
233 .unwrap_or(false);
234
235 let hit_min_x_bounds = wpos.x == self.bounds.min.x;
236 let hit_min_y_bounds = wpos.y == self.bounds.min.y;
237 let hit_max_x_bounds = wpos.x == self.bounds.max.x - 1;
238 let hit_max_y_bounds = wpos.y == self.bounds.max.y - 1;
239
240 let is_bounds =
241 hit_min_x_bounds || hit_min_y_bounds || hit_max_x_bounds || hit_max_y_bounds;
242
243 let is_corner = (hit_max_y_bounds || hit_min_y_bounds)
244 && (hit_max_x_bounds || hit_min_x_bounds)
245 && is_bounds;
246
247 if z_off == 1 && is_bounds {
248 let adjacent_type = if is_corner {
250 RelativeNeighborPosition::L
251 } else {
252 RelativeNeighborPosition::I
253 };
254
255 let ori = if !is_corner {
256 if hit_min_x_bounds || hit_max_x_bounds {
259 2
260 } else {
261 0
262 }
263 } else {
264 if hit_min_x_bounds && hit_min_y_bounds {
267 4
268 } else if hit_max_x_bounds && hit_min_y_bounds {
269 6
270 } else if hit_min_x_bounds && hit_max_y_bounds {
271 2
272 } else {
273 0
274 }
275 };
276
277 Some(
278 old.into_vacant()
279 .with_sprite(SpriteKind::FenceWoodWoodland)
280 .with_ori(ori)
281 .unwrap()
282 .with_adjacent_type(adjacent_type)
283 .unwrap(),
284 )
285 } else if is_bank {
286 None
288 } else if matches!(old.kind(), BlockKind::Grass | BlockKind::Sand) {
289 Some(Block::new(
291 if self.is_desert {
292 BlockKind::Sand
293 } else {
294 BlockKind::Grass
295 },
296 (Lerp::lerp(
297 col.surface_color,
298 col.sub_surface_color * 0.5,
299 is_trench as i32 as f32,
300 ) * 255.0)
301 .as_(),
302 ))
303 } else if z_off == 1 && (is_trench || self.crop.row_spacing().is_none()) {
304 self.crop
306 .sprites()
307 .choose_weighted(rng, |(w, _)| *w)
308 .ok()
309 .and_then(|&(_, s)| {
310 let new = old.into_vacant().with_sprite(s?);
311 let new = new.with_attr(Owned(true)).unwrap_or(new);
312
313 Some(new)
314 })
315 } else if z_off == 1 && rng.random_bool(0.001) {
316 Some(old.into_vacant().with_sprite(SpriteKind::Scarecrow))
317 } else {
318 None
319 }
320 }
321}