veloren_world/
block.rs

1use crate::{
2    CONFIG, IndexRef,
3    column::{ColumnGen, ColumnSample},
4    util::{FastNoise, RandomField, Sampler, SmallCache},
5};
6use common::{
7    calendar::{Calendar, CalendarEvent},
8    comp::item::ItemDefinitionIdOwned,
9    terrain::{
10        Block, BlockKind, SpriteCfg, SpriteKind, UnlockKind,
11        structure::{self, StructureBlock},
12    },
13};
14use core::ops::{Div, Mul, Range};
15use serde::Deserialize;
16use vek::*;
17
18type Gradients = Vec<Range<(u8, u8, u8)>>;
19
20#[derive(Deserialize)]
21pub struct Colors {
22    // TODO(@Sharp): After the merge, construct enough infrastructure to make it convenient to
23    // define mapping functions over the input; i.e. we should be able to interpret some fields as
24    // defining App<Abs<Fun, Type>, Arg>, where Fun : (Context, Arg) → (S, Type).
25    pub structure_blocks: structure::structure_block::PureCases<Option<Gradients>>,
26}
27
28pub struct BlockGen<'a> {
29    pub column_gen: ColumnGen<'a>,
30}
31
32impl<'a> BlockGen<'a> {
33    pub fn new(column_gen: ColumnGen<'a>) -> Self { Self { column_gen } }
34
35    pub fn sample_column<'b>(
36        column_gen: &ColumnGen<'a>,
37        cache: &'b mut SmallCache<Vec2<i32>, Option<ColumnSample<'a>>>,
38        wpos: Vec2<i32>,
39        index: IndexRef<'a>,
40        calendar: Option<&'a Calendar>,
41    ) -> Option<&'b ColumnSample<'a>> {
42        cache
43            .get(wpos, |wpos| column_gen.get((wpos, index, calendar)))
44            .as_ref()
45    }
46
47    pub fn get_z_cache(
48        &mut self,
49        wpos: Vec2<i32>,
50        index: IndexRef<'a>,
51        calendar: Option<&'a Calendar>,
52    ) -> Option<ZCache<'a>> {
53        let BlockGen { column_gen } = self;
54
55        // Main sample
56        let sample = column_gen.get((wpos, index, calendar))?;
57
58        Some(ZCache { sample, calendar })
59    }
60
61    pub fn get_with_z_cache(&mut self, wpos: Vec3<i32>, z_cache: Option<&ZCache>) -> Option<Block> {
62        let z_cache = z_cache?;
63        let sample = &z_cache.sample;
64        let &ColumnSample {
65            alt,
66            basement,
67            chaos,
68            water_level,
69            surface_color,
70            sub_surface_color,
71            stone_col,
72            snow_cover,
73            cliff_offset,
74            cliff_height,
75            ice_depth,
76            ..
77        } = sample;
78
79        let wposf = wpos.map(|e| e as f64);
80
81        // Sample blocks
82        let water = Block::new(BlockKind::Water, Rgb::zero());
83        let grass_depth = (1.5 + 2.0 * chaos).min(alt - basement);
84        if (wposf.z as f32) < alt - grass_depth {
85            let stone_factor = (alt - grass_depth - wposf.z as f32) * 0.15;
86            let col = Lerp::lerp(
87                sub_surface_color,
88                stone_col.map(|e| e as f32 / 255.0),
89                stone_factor,
90            )
91            .map(|e| (e * 255.0) as u8);
92
93            if stone_factor >= 0.5 {
94                if wposf.z as f32 > alt - cliff_offset.max(0.0) {
95                    if cliff_offset.max(0.0)
96                        > cliff_height
97                            - (FastNoise::new(37).get(wposf / Vec3::new(6.0, 6.0, 10.0)) * 0.5
98                                + 0.5)
99                                * (alt - grass_depth - wposf.z as f32)
100                                    .mul(0.25)
101                                    .clamped(0.0, 8.0)
102                    {
103                        Some(Block::empty())
104                    } else {
105                        let col = Lerp::lerp(
106                            col.map(|e| e as f32),
107                            col.map(|e| e as f32) * 0.7,
108                            (wposf.z as f32 - basement * 0.3).div(2.0).sin() * 0.5 + 0.5,
109                        )
110                        .map(|e| e as u8);
111                        Some(Block::new(BlockKind::Rock, col))
112                    }
113                } else {
114                    Some(Block::new(BlockKind::Rock, col))
115                }
116            } else {
117                Some(Block::new(BlockKind::Earth, col))
118            }
119        } else if wposf.z as i32 <= alt as i32 {
120            let grass_factor = (wposf.z as f32 - (alt - grass_depth))
121                .div(grass_depth)
122                .sqrt();
123            // Surface
124            Some(if water_level > alt.ceil() {
125                Block::new(
126                    BlockKind::Sand,
127                    sub_surface_color.map(|e| (e * 255.0) as u8),
128                )
129            } else {
130                let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor);
131                if grass_factor < 0.7 {
132                    Block::new(BlockKind::Earth, col.map(|e| (e * 255.0) as u8))
133                } else if snow_cover {
134                    //if temp < CONFIG.snow_temp + 0.031 {
135                    Block::new(BlockKind::Snow, col.map(|e| (e * 255.0) as u8))
136                } else {
137                    Block::new(BlockKind::Grass, col.map(|e| (e * 255.0) as u8))
138                }
139            })
140        } else {
141            None
142        }
143        .or_else(|| {
144            let over_water = alt < water_level;
145            // Water
146            if over_water && (wposf.z as f32 - water_level).abs() < ice_depth {
147                Some(Block::new(BlockKind::Ice, CONFIG.ice_color))
148            } else if (wposf.z as f32) < water_level {
149                // Ocean
150                Some(water)
151            } else {
152                None
153            }
154        })
155    }
156}
157
158pub struct ZCache<'a> {
159    pub sample: ColumnSample<'a>,
160    pub calendar: Option<&'a Calendar>,
161}
162
163impl ZCache<'_> {
164    pub fn get_z_limits(&self) -> (f32, f32) {
165        let min = self.sample.alt
166            - (self.sample.chaos.min(1.0) * 16.0)
167            - self.sample.cliff_offset.max(0.0);
168        let min = min - 4.0;
169
170        let warp = self.sample.chaos * 32.0;
171
172        let ground_max = self.sample.alt + warp + 2.0;
173
174        let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth);
175
176        (min, max)
177    }
178}
179
180fn ori_of_unit(unit: &Vec2<i32>) -> u8 {
181    if unit.y != 0 {
182        if unit.y < 0 { 6 } else { 2 }
183    } else if unit.x < 0 {
184        4
185    } else {
186        0
187    }
188}
189
190fn rotate_for_units(ori: u8, units: &Vec2<Vec2<i32>>) -> u8 {
191    let ox = ori_of_unit(&units.x);
192    let oy = ori_of_unit(&units.y);
193    let positive = ((ox + 8 - oy) & 4) != 0;
194    if positive {
195        // I have no idea why ox&2 is special here, but it is
196        (ox + ori + if (ox & 2) != 0 { 4 } else { 0 }) % 8
197    } else {
198        (ox + 8 - ori) % 8
199    }
200}
201
202pub fn block_from_structure(
203    index: IndexRef,
204    sblock: &StructureBlock,
205    pos: Vec3<i32>,
206    structure_pos: Vec2<i32>,
207    structure_seed: u32,
208    sample: &ColumnSample,
209    mut with_sprite: impl FnMut(SpriteKind) -> Block,
210    calendar: Option<&Calendar>,
211    units: &Vec2<Vec2<i32>>,
212) -> Option<(Block, Option<SpriteCfg>)> {
213    let field = RandomField::new(structure_seed);
214
215    let lerp = field.get_f32(Vec3::from(structure_pos)) * 0.8 + field.get_f32(pos) * 0.2;
216
217    let block = match sblock {
218        StructureBlock::None => None,
219        StructureBlock::Hollow => Some(Block::air(SpriteKind::Empty)),
220        StructureBlock::Grass => Some(Block::new(
221            BlockKind::Grass,
222            sample.surface_color.map(|e| (e * 255.0) as u8),
223        )),
224        StructureBlock::Normal(color) => Some(Block::new(BlockKind::Misc, *color)),
225        StructureBlock::Filled(kind, color) => Some(Block::new(*kind, *color)),
226        StructureBlock::Sprite(kind) => Some(with_sprite(*kind).into_vacant().with_sprite(*kind)),
227        StructureBlock::RotatedSprite(kind, ori) => Some(
228            with_sprite(*kind)
229                .into_vacant()
230                .with_sprite(*kind)
231                .with_ori(rotate_for_units(*ori, units))
232                .unwrap_or(Block::air(*kind)),
233        ),
234        StructureBlock::EntitySpawner(_entitykind, _spawn_chance) => {
235            Some(Block::new(BlockKind::Air, Rgb::new(255, 255, 255)))
236        },
237        StructureBlock::Water => Some(Block::water(SpriteKind::Empty)),
238        // TODO: If/when liquid supports other colors again, revisit this.
239        StructureBlock::GreenSludge => Some(Block::water(SpriteKind::Empty)),
240        // None of these BlockKinds has an orientation, so we just use zero for the other color
241        // bits.
242        StructureBlock::Liana => Some(with_sprite(SpriteKind::Liana)),
243        StructureBlock::Fruit => {
244            if field.get(pos + structure_pos) % 24 == 0 {
245                Some(with_sprite(SpriteKind::Beehive))
246            } else if field.get(pos + structure_pos + 1) % 3 == 0 {
247                Some(with_sprite(SpriteKind::Apple))
248            } else {
249                None
250            }
251        },
252        StructureBlock::Coconut => {
253            if field.get(pos + structure_pos) % 3 > 0 {
254                None
255            } else {
256                Some(with_sprite(SpriteKind::Coconut))
257            }
258        },
259        StructureBlock::Chest => {
260            let old_block = with_sprite(SpriteKind::Empty);
261            let block = if old_block.is_fluid() {
262                old_block
263            } else {
264                Block::air(SpriteKind::Empty)
265            };
266            if field.chance(pos + structure_pos, 0.5) {
267                Some(block)
268            } else {
269                Some(block.with_sprite(SpriteKind::Chest))
270            }
271        },
272        StructureBlock::Log => Some(Block::new(BlockKind::Wood, Rgb::new(60, 30, 0))),
273        // We interpolate all these BlockKinds as needed.
274        StructureBlock::TemperateLeaves
275        | StructureBlock::PineLeaves
276        | StructureBlock::FrostpineLeaves
277        | StructureBlock::PalmLeavesInner
278        | StructureBlock::PalmLeavesOuter
279        | StructureBlock::Acacia
280        | StructureBlock::Mangrove
281        | StructureBlock::Chestnut
282        | StructureBlock::Baobab
283        | StructureBlock::MapleLeaves
284        | StructureBlock::CherryLeaves
285        | StructureBlock::AutumnLeaves => {
286            if calendar.is_some_and(|c| c.is_event(CalendarEvent::Christmas))
287                && field.chance(pos + structure_pos, 0.025)
288            {
289                Some(Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0)))
290            } else if calendar.is_some_and(|c| c.is_event(CalendarEvent::Halloween))
291                && (*sblock == StructureBlock::TemperateLeaves
292                    || *sblock == StructureBlock::Chestnut
293                    || *sblock == StructureBlock::CherryLeaves)
294            {
295                crate::all::leaf_color(index, structure_seed, lerp, &StructureBlock::AutumnLeaves)
296                    .map(|col| Block::new(BlockKind::Leaves, col))
297            } else {
298                crate::all::leaf_color(index, structure_seed, lerp, sblock)
299                    .map(|col| Block::new(BlockKind::Leaves, col))
300            }
301        },
302        StructureBlock::BirchWood => {
303            let wpos = pos + structure_pos;
304            if field.chance(
305                (wpos + Vec3::new(wpos.z, wpos.z, 0) / 2)
306                    / Vec3::new(1 + wpos.z % 2, 1 + (wpos.z + 1) % 2, 1),
307                0.25,
308            ) && wpos.z % 2 == 0
309            {
310                Some(Block::new(BlockKind::Wood, Rgb::new(70, 35, 25)))
311            } else {
312                Some(Block::new(BlockKind::Wood, Rgb::new(220, 170, 160)))
313            }
314        },
315        StructureBlock::Keyhole(consumes) => {
316            return Some((
317                Block::air(SpriteKind::Keyhole),
318                Some(SpriteCfg {
319                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
320                        consumes.clone(),
321                    ))),
322                    ..SpriteCfg::default()
323                }),
324            ));
325        },
326        StructureBlock::Sign(content, ori) => {
327            return Some((
328                Block::air(SpriteKind::Sign)
329                    .with_ori(rotate_for_units(*ori, units))
330                    .expect("signs can always be rotated"),
331                Some(SpriteCfg {
332                    content: Some(content.clone()),
333                    ..SpriteCfg::default()
334                }),
335            ));
336        },
337        StructureBlock::BoneKeyhole(consumes) => {
338            return Some((
339                Block::air(SpriteKind::BoneKeyhole),
340                Some(SpriteCfg {
341                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
342                        consumes.clone(),
343                    ))),
344                    ..SpriteCfg::default()
345                }),
346            ));
347        },
348        StructureBlock::HaniwaKeyhole(consumes) => {
349            return Some((
350                Block::air(SpriteKind::HaniwaKeyhole),
351                Some(SpriteCfg {
352                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
353                        consumes.clone(),
354                    ))),
355                    ..SpriteCfg::default()
356                }),
357            ));
358        },
359        StructureBlock::KeyholeBars(consumes) => {
360            return Some((
361                Block::air(SpriteKind::KeyholeBars),
362                Some(SpriteCfg {
363                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
364                        consumes.clone(),
365                    ))),
366                    ..SpriteCfg::default()
367                }),
368            ));
369        },
370        StructureBlock::GlassKeyhole(consumes) => {
371            return Some((
372                Block::air(SpriteKind::GlassKeyhole),
373                Some(SpriteCfg {
374                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
375                        consumes.clone(),
376                    ))),
377                    ..SpriteCfg::default()
378                }),
379            ));
380        },
381        StructureBlock::TerracottaKeyhole(consumes) => {
382            return Some((
383                Block::air(SpriteKind::TerracottaKeyhole),
384                Some(SpriteCfg {
385                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
386                        consumes.clone(),
387                    ))),
388                    ..SpriteCfg::default()
389                }),
390            ));
391        },
392        StructureBlock::SahaginKeyhole(consumes) => {
393            return Some((
394                Block::air(SpriteKind::SahaginKeyhole),
395                Some(SpriteCfg {
396                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
397                        consumes.clone(),
398                    ))),
399                    ..SpriteCfg::default()
400                }),
401            ));
402        },
403        StructureBlock::VampireKeyhole(consumes) => {
404            return Some((
405                Block::air(SpriteKind::VampireKeyhole),
406                Some(SpriteCfg {
407                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
408                        consumes.clone(),
409                    ))),
410                    ..SpriteCfg::default()
411                }),
412            ));
413        },
414
415        StructureBlock::MyrmidonKeyhole(consumes) => {
416            return Some((
417                Block::air(SpriteKind::MyrmidonKeyhole),
418                Some(SpriteCfg {
419                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
420                        consumes.clone(),
421                    ))),
422                    ..SpriteCfg::default()
423                }),
424            ));
425        },
426        StructureBlock::MinotaurKeyhole(consumes) => {
427            return Some((
428                Block::air(SpriteKind::MinotaurKeyhole),
429                Some(SpriteCfg {
430                    unlock: Some(UnlockKind::Consumes(ItemDefinitionIdOwned::Simple(
431                        consumes.clone(),
432                    ))),
433                    ..SpriteCfg::default()
434                }),
435            ));
436        },
437        StructureBlock::RedwoodWood => {
438            let wpos = pos + structure_pos;
439            if (wpos.x / 2 + wpos.y) % 5 > 1 && ((wpos.x + 1) / 2 + wpos.y + 2) % 5 > 1 {
440                Some(Block::new(BlockKind::Wood, Rgb::new(80, 40, 10)))
441            } else {
442                Some(Block::new(BlockKind::Wood, Rgb::new(110, 55, 10)))
443            }
444        },
445    };
446
447    Some((block?, None))
448}