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