1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::ops::Range;
use std::sync::RwLock;

use serde::Deserialize;

use crate::{biome::Biome, Block, Chunk, HeightMode};
use crate::{expand_heightmap, Heightmaps, Section, SectionTower};

use super::AIR;

impl Chunk for CurrentJavaChunk {
    fn status(&self) -> String {
        self.status.clone()
    }

    fn surface_height(&self, x: usize, z: usize, mode: HeightMode) -> isize {
        let mut heightmap = self.lazy_heightmap.read().unwrap();
        if heightmap.is_none() {
            drop(heightmap);
            self.recalculate_heightmap(mode);
            heightmap = self.lazy_heightmap.read().unwrap();
        }
        heightmap.unwrap()[z * 16 + x] as isize
    }

    fn biome(&self, x: usize, y: isize, z: usize) -> Option<Biome> {
        // Going to be awkward because the biomes are now paletted, and so are
        // the string not a number.

        let sections = self.sections.as_ref()?;
        let sec = sections.get_section_for_y(y)?;
        let sec_y = (y - sec.y as isize * 16) as usize;

        sec.biomes.at(x, sec_y, z).cloned()
    }

    fn block(&self, x: usize, y: isize, z: usize) -> Option<&Block> {
        let sections = self.sections.as_ref()?;
        let sec = sections.get_section_for_y(y)?;
        let sec_y = (y - sec.y as isize * 16) as usize;

        Some(sec.block_states.at(x, sec_y, z).unwrap_or(&AIR))
    }

    fn y_range(&self) -> Range<isize> {
        match &self.sections {
            Some(sections) => Range {
                start: sections.y_min(),
                end: sections.y_max(),
            },
            None => Range { start: 0, end: 0 },
        }
    }
}

/// A Minecraft chunk.
///
/// This type is not designed for accessing all data of a chunk. It provides the
/// data necessary to render maps for fastanvil. If you need more complete data
/// you need to write your own chunk struct and implement the serde traits, or
/// use [`fastnbt::Value`].
#[derive(Deserialize, Debug)]
pub struct CurrentJavaChunk {
    #[serde(rename = "DataVersion")]
    pub data_version: i32,

    // Maybe put section and heightmaps together and serde flatten?
    pub sections: Option<SectionTower<Section>>,

    #[serde(rename = "Heightmaps")]
    pub heightmaps: Option<Heightmaps>,

    #[serde(rename = "Status")]
    pub status: String,

    #[serde(skip)]
    pub(crate) lazy_heightmap: RwLock<Option<[i16; 256]>>,
}

impl CurrentJavaChunk {
    pub fn recalculate_heightmap(&self, mode: HeightMode) {
        // TODO: Find top section and start there, pointless checking 320 down
        // if its a 1.16 chunk.

        let mut map = [0; 256];

        match mode {
            HeightMode::Trust => {
                let updated = self
                    .heightmaps
                    .as_ref()
                    .and_then(|hm| hm.motion_blocking.as_ref())
                    .map(|hm| {
                        let y_min = self.sections.as_ref().unwrap().y_min();
                        expand_heightmap(hm, y_min, self.data_version)
                    })
                    .map(|hm| map.copy_from_slice(hm.as_slice()))
                    .is_some();

                if updated {
                    *self.lazy_heightmap.write().unwrap() = Some(map);
                    return;
                }
            }
            HeightMode::Calculate => {} // fall through to calc mode
        }

        let y_range = self.y_range();
        let y_end = y_range.end;

        for z in 0..16 {
            for x in 0..16 {
                // start at top until we hit a non-air block.
                for i in y_range.clone() {
                    let y = y_end - i;
                    let block = self.block(x, y - 1, z);

                    if block.is_none() {
                        continue;
                    }

                    if !["minecraft:air", "minecraft:cave_air"]
                        .as_ref()
                        .contains(&block.unwrap().name())
                    {
                        map[z * 16 + x] = y as i16;
                        break;
                    }
                }
            }
        }

        *self.lazy_heightmap.write().unwrap() = Some(map);
    }
}