veloren_world/site2/plot/
farm_field.rs

1use 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    // None => no rows
29    // Some((row width, row coverage proportion)) =>
30    fn row_spacing(&self) -> Option<(f32, f32)> {
31        match self {
32            Self::Wildflower => None,
33            // Grains
34            Self::Wheat | Self::Flax | Self::Corn => Some((6.0, 0.8)),
35            // Bushes
36            Self::Tomato | Self::Cactus => Some((3.0, 1.0 / 3.0)),
37            // Root & brassica
38            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
115/// Represents house data generated by the `generate()` method
116pub struct FarmField {
117    crop: Crop,
118    /// Axis aligned bounding region for the house
119    bounds: Aabr<i32>,
120    /// Approximate altitude of the door tile
121    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            // soil
199            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            // fence
214            let adjacent_type = if is_corner {
215                RelativeNeighborPosition::L
216            } else {
217                RelativeNeighborPosition::I
218            };
219
220            let ori = if !is_corner {
221                // for straight - "I"
222                // can only go in the vertical or horizontal direction
223                if hit_min_x_bounds || hit_max_x_bounds {
224                    2
225                } else {
226                    0
227                }
228            } else {
229                // for corners - "L"
230                // can be rotated in 4 different directions
231                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            // crops
252            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}