veloren_voxygen/scene/terrain/
watcher.rs

1use crate::hud::CraftingTab;
2use common::{
3    terrain::{Block, BlockKind, SpriteKind, sprite},
4    vol::ReadVol,
5};
6use common_base::span;
7use rand::prelude::*;
8use rand_chacha::ChaCha8Rng;
9use vek::*;
10
11#[derive(Clone, Copy, Debug)]
12pub enum Interaction {
13    /// This covers mining, unlocking, and regular collectable things (e.g.
14    /// twigs).
15    Collect,
16    Craft(CraftingTab),
17    Mount,
18    Read,
19    LightToggle(bool),
20}
21
22#[derive(Copy, Clone)]
23pub enum FireplaceType {
24    House,
25    Workshop, // this also includes witch hut
26}
27
28pub struct SmokerProperties {
29    pub position: Vec3<i32>,
30    pub kind: FireplaceType,
31}
32
33impl SmokerProperties {
34    fn new(position: Vec3<i32>, kind: FireplaceType) -> Self { Self { position, kind } }
35}
36
37#[derive(Default)]
38pub struct BlocksOfInterest {
39    pub leaves: Vec<Vec3<i32>>,
40    pub drip: Vec<Vec3<i32>>,
41    pub grass: Vec<Vec3<i32>>,
42    pub slow_river: Vec<Vec3<i32>>,
43    pub fast_river: Vec<Vec3<i32>>,
44    pub waterfall: Vec<(Vec3<i32>, Vec3<f32>)>,
45    pub lavapool: Vec<Vec3<i32>>,
46    pub fires: Vec<Vec3<i32>>,
47    pub smokers: Vec<SmokerProperties>,
48    pub beehives: Vec<Vec3<i32>>,
49    pub reeds: Vec<Vec3<i32>>,
50    pub fireflies: Vec<Vec3<i32>>,
51    pub flowers: Vec<Vec3<i32>>,
52    pub fire_bowls: Vec<Vec3<i32>>,
53    pub snow: Vec<Vec3<i32>>,
54    pub spores: Vec<Vec3<i32>>,
55    //This is so crickets stay in place and don't randomly change sounds
56    pub cricket1: Vec<Vec3<i32>>,
57    pub cricket2: Vec<Vec3<i32>>,
58    pub cricket3: Vec<Vec3<i32>>,
59    pub frogs: Vec<Vec3<i32>>,
60    pub one_way_walls: Vec<(Vec3<i32>, Vec3<f32>)>,
61    // Note: these are only needed for chunks within the iteraction range so this is a potential
62    // area for optimization
63    pub interactables: Vec<(Vec3<i32>, Interaction)>,
64    pub lights: Vec<(Vec3<i32>, u8)>,
65    pub train_smokes: Vec<Vec3<i32>>,
66    // needed for biome specific smoke variations
67    pub temperature: f32,
68    pub humidity: f32,
69}
70
71impl BlocksOfInterest {
72    pub fn from_blocks(
73        blocks: impl Iterator<Item = (Vec3<i32>, Block)>,
74        river_velocity: Vec3<f32>,
75        temperature: f32,
76        humidity: f32,
77        chunk: &impl ReadVol<Vox = Block>,
78    ) -> Self {
79        span!(_guard, "from_chunk", "BlocksOfInterest::from_chunk");
80        let mut leaves = Vec::new();
81        let mut drip = Vec::new();
82        let mut grass = Vec::new();
83        let mut slow_river = Vec::new();
84        let mut fast_river = Vec::new();
85        let mut waterfall = Vec::new();
86        let mut lavapool = Vec::new();
87        let mut fires = Vec::new();
88        let mut smokers = Vec::new();
89        let mut beehives = Vec::new();
90        let mut reeds = Vec::new();
91        let mut fireflies = Vec::new();
92        let mut flowers = Vec::new();
93        let mut interactables = Vec::new();
94        let mut lights = Vec::new();
95        // Lights that can be omitted at random if we have too many and need to cull
96        // some of them
97        let mut minor_lights = Vec::new();
98        let mut fire_bowls = Vec::new();
99        let mut snow = Vec::new();
100        let mut cricket1 = Vec::new();
101        let mut cricket2 = Vec::new();
102        let mut cricket3 = Vec::new();
103        let mut frogs = Vec::new();
104        let mut one_way_walls = Vec::new();
105        let mut spores = Vec::new();
106        let mut train_smokes = Vec::new();
107
108        let mut rng = ChaCha8Rng::from_seed(thread_rng().gen());
109
110        blocks.for_each(|(pos, block)| {
111            match block.kind() {
112                BlockKind::Leaves
113                    if rng.gen_range(0..16) == 0
114                        && chunk
115                            .get(pos - Vec3::unit_z())
116                            .map_or(true, |b| !b.is_filled()) =>
117                {
118                    leaves.push(pos)
119                },
120                BlockKind::WeakRock if rng.gen_range(0..6) == 0 => drip.push(pos),
121                BlockKind::Grass => {
122                    if rng.gen_range(0..16) == 0 {
123                        grass.push(pos);
124                    }
125                    match rng.gen_range(0..8192) {
126                        1 => cricket1.push(pos),
127                        2 => cricket2.push(pos),
128                        3 => cricket3.push(pos),
129                        _ => {},
130                    }
131                },
132                BlockKind::Water => {
133                    let is_waterfall = chunk
134                        .get(pos + vek::Vec3::unit_z())
135                        .is_ok_and(|b| b.is_air())
136                        && [
137                            vek::Vec2::new(0, 1),
138                            vek::Vec2::new(1, 0),
139                            vek::Vec2::new(0, -1),
140                            vek::Vec2::new(-1, 0),
141                        ]
142                        .iter()
143                        .map(|p| {
144                            (1..=2)
145                                .take_while(|i| {
146                                    chunk.get(pos + p.with_z(*i)).is_ok_and(|b| b.is_liquid())
147                                })
148                                .count()
149                        })
150                        .any(|s| s >= 2);
151
152                    if is_waterfall {
153                        waterfall.push((pos, river_velocity));
154                    }
155
156                    let river_speed_sq = river_velocity.magnitude_squared();
157                    // Assign a river speed to water blocks depending on river velocity
158                    if is_waterfall || river_speed_sq > 0.9_f32.powi(2) {
159                        fast_river.push(pos)
160                    } else if river_speed_sq > 0.3_f32.powi(2) {
161                        slow_river.push(pos)
162                    }
163                },
164                BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos),
165                BlockKind::Lava
166                    if chunk
167                        .get(pos + Vec3::unit_z())
168                        .map_or(true, |b| !b.is_filled()) =>
169                {
170                    if rng.gen_range(0..5) == 0 {
171                        fires.push(pos + Vec3::unit_z())
172                    }
173                    if rng.gen_range(0..16) == 0 {
174                        lavapool.push(pos)
175                    }
176                },
177                BlockKind::GlowingMushroom if rng.gen_range(0..8) == 0 => spores.push(pos),
178                BlockKind::Snow | BlockKind::Ice if rng.gen_range(0..16) == 0 => snow.push(pos),
179                _ => {
180                    if let Some(sprite) = block.get_sprite() {
181                        if sprite.category() == sprite::Category::Lamp {
182                            if let Ok(sprite::LightEnabled(enabled)) = block.get_attr() {
183                                interactables.push((pos, Interaction::LightToggle(!enabled)));
184                            }
185                        }
186
187                        if block.is_mountable() {
188                            interactables.push((pos, Interaction::Mount));
189                        }
190
191                        match sprite {
192                            SpriteKind::Ember => {
193                                fires.push(pos);
194                                smokers.push(SmokerProperties::new(pos, FireplaceType::House));
195                            },
196                            SpriteKind::TrainSmoke => {
197                                train_smokes.push(pos);
198                            },
199                            SpriteKind::FireBlock => {
200                                fire_bowls.push(pos);
201                            },
202                            // Offset positions to account for block height.
203                            // TODO: Is this a good idea?
204                            SpriteKind::StreetLamp => fire_bowls.push(pos + Vec3::unit_z() * 2),
205                            SpriteKind::FireBowlGround => fire_bowls.push(pos + Vec3::unit_z()),
206                            SpriteKind::StreetLampTall => fire_bowls.push(pos + Vec3::unit_z() * 4),
207                            SpriteKind::WallSconce => fire_bowls.push(pos + Vec3::unit_z()),
208                            SpriteKind::Beehive => beehives.push(pos),
209                            SpriteKind::Reed => {
210                                reeds.push(pos);
211                                fireflies.push(pos);
212                                if rng.gen_range(0..12) == 0 {
213                                    frogs.push(pos);
214                                }
215                            },
216                            SpriteKind::CaveMushroom => fireflies.push(pos),
217                            SpriteKind::PinkFlower => flowers.push(pos),
218                            SpriteKind::PurpleFlower => flowers.push(pos),
219                            SpriteKind::RedFlower => flowers.push(pos),
220                            SpriteKind::WhiteFlower => flowers.push(pos),
221                            SpriteKind::YellowFlower => flowers.push(pos),
222                            SpriteKind::Sunflower => flowers.push(pos),
223                            SpriteKind::CraftingBench => {
224                                interactables.push((pos, Interaction::Craft(CraftingTab::All)))
225                            },
226                            SpriteKind::SmokeDummy => {
227                                smokers.push(SmokerProperties::new(pos, FireplaceType::Workshop));
228                            },
229                            SpriteKind::Forge => interactables
230                                .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),
231                            SpriteKind::TanningRack => interactables
232                                .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),
233                            SpriteKind::SpinningWheel => {
234                                interactables.push((pos, Interaction::Craft(CraftingTab::All)))
235                            },
236                            SpriteKind::Loom => {
237                                interactables.push((pos, Interaction::Craft(CraftingTab::All)))
238                            },
239                            SpriteKind::Cauldron => {
240                                fires.push(pos);
241                                interactables.push((pos, Interaction::Craft(CraftingTab::Potion)))
242                            },
243                            SpriteKind::Anvil => {
244                                interactables.push((pos, Interaction::Craft(CraftingTab::Weapon)))
245                            },
246                            SpriteKind::CookingPot => {
247                                fires.push(pos);
248                                interactables.push((pos, Interaction::Craft(CraftingTab::Food)))
249                            },
250                            SpriteKind::DismantlingBench => {
251                                fires.push(pos);
252                                interactables
253                                    .push((pos, Interaction::Craft(CraftingTab::Dismantle)))
254                            },
255                            SpriteKind::RepairBench => {
256                                interactables.push((pos, Interaction::Craft(CraftingTab::All)))
257                            },
258                            SpriteKind::OneWayWall => one_way_walls.push((
259                                pos,
260                                Vec2::unit_y()
261                                    .rotated_z(
262                                        std::f32::consts::PI
263                                            * 0.25
264                                            * block
265                                                .get_attr::<sprite::Ori>()
266                                                .unwrap_or(sprite::Ori(0))
267                                                .0
268                                                as f32,
269                                    )
270                                    .with_z(0.0),
271                            )),
272                            SpriteKind::Sign | SpriteKind::HangingSign => {
273                                interactables.push((pos, Interaction::Read))
274                            },
275                            SpriteKind::MycelBlue => spores.push(pos),
276                            SpriteKind::Mold => spores.push(pos),
277                            _ => {},
278                        }
279                    }
280                },
281            }
282            // NOTE: we don't care whether it requires mine-tool or not here
283            if block.default_tool().is_some() {
284                interactables.push((pos, Interaction::Collect));
285            }
286            if let Some(glow) = block.get_glow() {
287                // Currently, we count filled blocks as 'minor' lights, and sprites as
288                // non-minor.
289                if block.get_sprite().is_none() {
290                    minor_lights.push((pos, glow));
291                } else {
292                    lights.push((pos, glow));
293                }
294            }
295        });
296
297        // TODO: Come up with a better way to prune many light sources: grouping them
298        // into larger lights with k-means clustering, perhaps?
299        const MAX_MINOR_LIGHTS: usize = 64;
300        lights.extend(
301            minor_lights
302                .choose_multiple(&mut rng, MAX_MINOR_LIGHTS)
303                .copied(),
304        );
305
306        Self {
307            leaves,
308            drip,
309            grass,
310            slow_river,
311            fast_river,
312            waterfall,
313            lavapool,
314            fires,
315            smokers,
316            beehives,
317            reeds,
318            fireflies,
319            flowers,
320            fire_bowls,
321            snow,
322            spores,
323            cricket1,
324            cricket2,
325            cricket3,
326            frogs,
327            one_way_walls,
328            interactables,
329            lights,
330            temperature,
331            humidity,
332            train_smokes,
333        }
334    }
335}