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