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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! This module contains all structs related to the nbt data in the chunks
//!
//! Every field for all structs in this class have been renamed to snake_case from whichever case
//! Mojang used.
//!
//! <rant>  
//! Mojang has the worst naming conventions ever! Sometimes they use snake_case, sometimes they use
//! PascalCase, other times they use camelCase, sometimes it's SCREAMING_SNAKE_CASE!  This is so
//! annoying when dealing with Mojang things!  Feel free to look at the name changes on _almost
//! everything_ just to make it happy and you'll be just as annoyed as I am!  
//! </rant>

use fastnbt::{self, IntArray, LongArray, Value};
use serde::Deserialize;

/// The represents that chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
pub struct ChunkNbt {
    #[serde(rename = "DataVersion")]
    pub data_version: i32,
    #[serde(rename = "Level")]
    pub level: Level,
}

impl ChunkNbt {
    //pub fn get_block(&self, pos: super::BlockPosition) -> anyhow::Result<BlockState> {
    //    dbg!(&pos);

    //    let section = pos.y / 16; // Sections are 16 block tall
    //    let section = self
    //        .level
    //        .sections
    //        .iter()
    //        .find(|sc| sc.y == section as i8)
    //        .context("sub-chunk does not exist.")?;

    //    // format is yxz
    //    let index = (pos.y % 16) * 16 * 16 + (pos.z % 16) * 16 + (pos.x % 16);

    //    let data = &section.block_states;

    //    // The count of bits for each palette entry
    //    let bit_count = ((section.palette.len() as f32).log2().ceil() as usize).max(4);

    //    let f = change_array_elt_size(data, bit_count);

    //    dbg!(&f);
    //    todo!()
    //}
}

//const fn nibbles(byte: u8) -> (u8, u8) {
//    (0xf0 & byte, 0x0f & byte)
//}

///// This _theoretically_ should not need more than 12 bits, but I'm not acutally 100% sure on that.
//fn change_array_elt_size(data: &LongArray, bit_count: usize) -> Vec<u16> {
//    let values_per_long = i64::BITS as usize / bit_count;
//
//    let mask = (1 << bit_count) - 1;
//
//    let mut out = Vec::with_capacity(data.len() * values_per_long);
//    for long in data.iter() {
//        for i in 0..values_per_long {
//            let item = mask & (long >> (i * bit_count));
//            out.push(item as u16);
//        }
//    }
//    out
//}

/// This does not contain _all_ of the values associated with the level, due to the fact that the
/// mcwiki has outdated information on this.  These are just some of the values that I got while
/// digging through the data myself.
// TODO: Add the rest of the fields
#[derive(Deserialize, Debug)]
pub struct Level {
    // TODO: This is probably an enum
    #[serde(rename = "Status")]
    pub status: String,
    #[serde(rename = "zPos")]
    pub z_pos: i32,
    #[serde(rename = "LastUpdate")]
    pub last_update: i64,
    #[serde(rename = "starlight.light_version")]
    pub starlight_light_version: i32,
    #[serde(rename = "Biomes")]
    pub biomes: IntArray,
    #[serde(rename = "InhabitedTime")]
    pub inhabited_time: i64,
    #[serde(rename = "xPos")]
    pub x_pos: i32,
    #[serde(rename = "HeightMaps")]
    pub height_maps: Option<Heightmaps>,
    #[serde(rename = "TileEntities")]
    pub tile_entities: Vec<Value>, // TODO: Can probably be replaced with an enum
    #[serde(rename = "isLightOn")]
    pub is_light_on: bool,
    #[serde(rename = "TileTicks")]
    pub tile_ticks: Vec<Value>, // TODO: I believe this actually has a specific format.
    #[serde(rename = "Sections")]
    pub sections: Vec<Value>,
}

// byte Nibble4(byte[] arr, int index) {
// 	return index%2 == 0 ? arr[index/2]&0x0F : (arr[index/2]>>4)&0x0F;
// }
//
// int BlockPos = y*16*16 + z*16 + x;
//
// compound Block = Palette[change_array_element_size(BlockStates,Log2(length(Palette)))[BlockPos]];
//
// string BlockName = Block.Name;
//
// compound BlockState = Block.Properties;
//
// byte Blocklight = Nibble4(BlockLight, BlockPos);
//
// byte Skylight = Nibble4(SkyLight, BlockPos);

/// The represents part of a chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub struct Heightmaps {
    pub motion_blocking: Option<LongArray>,
    pub motion_blocking_no_leaves: Option<LongArray>,
    pub ocean_floor: Option<LongArray>,
    pub ocean_floor_wg: Option<LongArray>,
    pub world_surface: Option<LongArray>,
    pub world_surface_wg: Option<LongArray>,
}

/// The represents a section(subchunk) from a chunk's nbt data stored in the region file
///
/// This does _not_ contain all fields due to the incorrect information on the wiki.
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ChunkSection {
    pub block_states: Option<LongArray>,
    pub palette: Option<Value>, // TODO: Can probably become an enum
    pub y: i8,
}

/// The represents part of a chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BlockStates {
    pub palette: Vec<BlockState>,
    pub data: Option<LongArray>,
}

/// The represents part of a chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
pub struct BlockState {
    #[serde(rename = "Name")]
    pub name: String,
    #[serde(rename = "Properties")]
    pub properties: Option<Value>,
}

/// The represents part of a chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
pub struct Biomes {
    pub palette: Vec<String>,
    pub data: Option<LongArray>,
}

/// The represents part of a chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#NBT_structure>
#[derive(Deserialize, Debug)]
pub struct Biome {
    #[serde(rename = "Name")]
    pub name: String,
}

/// This represents part of a chunk's nbt data stored in the region file
///
/// See <https://minecraft.fandom.com/wiki/Chunk_format#Tile_tick_format>
#[derive(Deserialize, Debug)]
pub struct TileTick {
    /// The ID of the block; used to activate the correct block update procedure.
    pub i: String,
    /// If multiple tile ticks are scheduled for the same tick, tile ticks with lower p are processed first. If they also have the same p, the order is unknown.
    pub p: i32,
    /// The number of ticks until processing should occur. May be negative when processing is overdue.
    pub t: i32,
    pub x: i32,
    pub y: i32,
    pub z: i32,
}