veloren_world/site2/plot/
farm_field.rs1use super::*;
2use crate::{ColumnSample, Land};
3use common::terrain::{
4 Block, BlockKind, SpriteKind,
5 sprite::{Owned, RelativeNeighborPosition},
6};
7use rand::prelude::*;
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 Cabbage,
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::Cabbage | 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::Cabbage => &[
90 (5.0, Some(SpriteKind::Empty)),
91 (1.0, Some(SpriteKind::Cabbage)),
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}
125
126impl FarmField {
127 pub fn generate(
128 land: &Land,
129 rng: &mut impl Rng,
130 site: &Site,
131 door_tile: Vec2<i32>,
132 door_dir: Vec2<i32>,
133 tile_aabr: Aabr<i32>,
134 is_desert: bool,
135 ) -> Self {
136 let bounds = Aabr {
137 min: site.tile_wpos(tile_aabr.min),
138 max: site.tile_wpos(tile_aabr.max),
139 };
140
141 let ori = rng.gen_range(0.0..std::f32::consts::TAU);
142
143 let crop = if is_desert {
144 Crop::Cactus
145 } else {
146 Crop::iter()
147 .filter(|crop| !matches!(crop, Crop::Cactus))
148 .choose(rng)
149 .unwrap()
150 };
151
152 Self {
153 bounds,
154 alt: land.get_alt_approx(site.tile_center_wpos(door_tile + door_dir)) as i32,
155 ori: Vec2::new(ori.sin(), ori.cos()),
156 crop,
157 is_desert,
158 }
159 }
160}
161
162impl Structure for FarmField {
163 #[cfg(feature = "use-dyn-lib")]
164 const UPDATE_FN: &'static [u8] = b"render_farmfield\0";
165
166 #[cfg_attr(feature = "be-dyn-lib", export_name = "render_farmfield")]
167 fn render_inner(&self, _site: &Site, _land: &Land, _painter: &Painter) {}
168
169 fn terrain_surface_at<R: Rng>(
170 &self,
171 wpos: Vec2<i32>,
172 old: Block,
173 rng: &mut R,
174 col: &ColumnSample,
175 z_off: i32,
176 _site: &Site,
177 ) -> Option<Block> {
178 let t = (self.ori * wpos.as_()).magnitude();
179 let is_trench = self
180 .crop
181 .row_spacing()
182 .map(|(w, p)| (t / w).fract() <= p)
183 .unwrap_or(false);
184
185 let hit_min_x_bounds = wpos.x == self.bounds.min.x;
186 let hit_min_y_bounds = wpos.y == self.bounds.min.y;
187 let hit_max_x_bounds = wpos.x == self.bounds.max.x - 1;
188 let hit_max_y_bounds = wpos.y == self.bounds.max.y - 1;
189
190 let is_bounds =
191 hit_min_x_bounds || hit_min_y_bounds || hit_max_x_bounds || hit_max_y_bounds;
192
193 let is_corner = (hit_max_y_bounds || hit_min_y_bounds)
194 && (hit_max_x_bounds || hit_min_x_bounds)
195 && is_bounds;
196
197 if z_off == 0 {
198 Some(Block::new(
200 if self.is_desert {
201 BlockKind::Sand
202 } else {
203 BlockKind::Grass
204 },
205 (Lerp::lerp(
206 col.surface_color,
207 col.sub_surface_color * 0.5,
208 is_trench as i32 as f32,
209 ) * 255.0)
210 .as_(),
211 ))
212 } else if z_off == 1 && is_bounds {
213 let adjacent_type = if is_corner {
215 RelativeNeighborPosition::L
216 } else {
217 RelativeNeighborPosition::I
218 };
219
220 let ori = if !is_corner {
221 if hit_min_x_bounds || hit_max_x_bounds {
224 2
225 } else {
226 0
227 }
228 } else {
229 if hit_min_x_bounds && hit_min_y_bounds {
232 4
233 } else if hit_max_x_bounds && hit_min_y_bounds {
234 6
235 } else if hit_min_x_bounds && hit_max_y_bounds {
236 2
237 } else {
238 0
239 }
240 };
241
242 Some(
243 old.into_vacant()
244 .with_sprite(SpriteKind::Fence)
245 .with_ori(ori)
246 .unwrap()
247 .with_adjacent_type(adjacent_type)
248 .unwrap(),
249 )
250 } else if z_off == 1 && (is_trench || self.crop.row_spacing().is_none()) {
251 self.crop
253 .sprites()
254 .choose_weighted(rng, |(w, _)| *w)
255 .ok()
256 .and_then(|&(_, s)| {
257 let new = old.into_vacant().with_sprite(s?);
258 let new = new.with_attr(Owned(true)).unwrap_or(new);
259
260 Some(new)
261 })
262 } else if z_off == 1 && rng.gen_bool(0.001) {
263 Some(old.into_vacant().with_sprite(SpriteKind::Scarecrow))
264 } else {
265 None
266 }
267 }
268}