simple_anvil/chunk.rs
1use nbt::{Blob, Value};
2
3use crate::{block::Block, region::Region};
4
5use std::{cmp, collections::HashMap};
6
7/// A simple representation of a Minecraft Chunk
8#[derive(Clone)]
9pub struct Chunk {
10 /// All of the chunk data
11 pub data: Box<Blob>,
12 /// The region x
13 pub x: u32,
14 /// The region z
15 pub z: u32,
16}
17
18impl Chunk {
19
20 /// Returns the chunk at an x,z coordinate within a Region.
21 ///
22 /// # Arguments
23 ///
24 /// * `region` - The Region from which to get the Chunk
25 /// * `chunk_x` - The x coordinate within the Region of the Chunk
26 /// * `chunk_z` - The z coordinate within the Region of the Chunk
27 pub fn from_region(region: &Region, chunk_x: u32, chunk_z: u32) -> Option<Chunk> {
28 match region.chunk_data(chunk_x, chunk_z) {
29 Some(data) => {
30 return Some(Chunk{ data, x: chunk_x, z: chunk_z });
31 }
32 None => None,
33 }
34 }
35
36 /// Returns a string representing the current generation state of the Chunk. 'full' is completely generated.
37 ///
38 /// # Examples
39 ///
40 /// ```rust,no_run
41 /// use simple_anvil::region::Region;
42 /// let region = Region::from_file("r.0.0.mca");
43 /// let chunk = region.get_chunk(0, 0).unwrap();
44 /// if chunk.get_status() == "full" {
45 /// println!("Fully Generated!");
46 /// }
47 /// ```
48 pub fn get_status(&self) -> &String {
49 if let Value::String(s) = self.data.get("Status").unwrap() {
50 s
51 } else {
52 panic!("Value should be a string?")
53 }
54 }
55
56 /// Returns an i64 (equivalent of Java long) of the last tick at which the chunk updated.
57 ///
58 /// # Examples
59 ///
60 /// ```rust,no_run
61 /// use simple_anvil::region::Region;
62 /// let region = Region::from_file("r.0.0.mca");
63 /// let chunk = region.get_chunk(0, 0).unwrap();
64 /// println!("{}", chunk.get_last_update());
65 /// ```
66 pub fn get_last_update(&self) -> &i64 {
67 if let Value::Long(l) = self.data.get("LastUpdate").unwrap() {
68 l
69 } else {
70 panic!("Value should be a i64")
71 }
72 }
73
74 /// Returns a heightmap of the Chunk. If the Chunk is not fully generated then a None is returned.
75 ///
76 /// # Arguments
77 ///
78 /// * `ignore_water` - Determines which heightmap to return, if true then a heightmap that does not take into account the water is returned (OCEAN_FLOOR), if false then the water is accounted for (WORLD_SURFACE).
79 ///
80 /// # Examples
81 ///
82 /// ```rust,no_run
83 /// use simple_anvil::region::Region;
84 /// let region = Region::from_file("r.0.0.mca");
85 /// let chunk = region.get_chunk(0, 0).unwrap();
86 /// let heightmap = chunk.get_heightmap(false);
87 /// ```
88 pub fn get_heightmap(&self, ignore_water: bool) -> Option<Vec<i32>> {
89 if self.get_status() == "full" {
90 let height_maps = if let Value::Compound(hm) = self.data.get("Heightmaps").unwrap() {
91 hm
92 } else {
93 panic!()
94 };
95
96 let map = if ignore_water {
97 "OCEAN_FLOOR"
98 } else {
99 "WORLD_SURFACE"
100 };
101
102 let surface = if let Value::LongArray(la) = height_maps.get(map).unwrap() {
103 la
104 } else {
105 panic!("no ocean?")
106 };
107
108 let surface_binary: Vec<String> = surface.iter().map(|n| format!("{:b}", n)).map(|n| "0".repeat(63 - n.len()) + &n).collect();
109 let mut all = Vec::new();
110 // let mut hmm = Vec::new();
111
112 for num in surface_binary {
113 let num_chars = num.chars().collect::<Vec<_>>();
114 let mut sub_nums = num_chars.chunks(9).collect::<Vec<&[char]>>();
115 sub_nums.reverse();
116 for num in sub_nums {
117 let test = num.iter().collect::<String>();
118 if test != "000000000" {
119 all.push(test.clone());
120 }
121 }
122 }
123
124 let mut heights = Vec::new();
125
126 for num in all {
127 let n = usize::from_str_radix(num.as_str(), 2).unwrap();
128 heights.push(n as i32 - 64 - 1);
129 }
130
131 return Some(heights);
132 } else {
133 None
134 }
135 }
136
137 /// Returns a vertical section of a Chunk
138 ///
139 /// # Arguments
140 ///
141 /// * `y` - The y index of the section.
142 fn get_section(&self, y: i8) -> Option<HashMap<String, Value>> {
143 if y < -4 || y > 19 {
144 panic!("Y value out of range")
145 }
146 let sections = if let Value::List(s) = self.data.get("sections").unwrap() {
147 s
148 } else {
149 panic!("Value should be a list?")
150 };
151
152 for section in sections {
153 let section = if let Value::Compound(s) = section {
154 s
155 } else {
156 panic!("should be a compound")
157 };
158 let section_y = if let Value::Byte(sec_y) = section.get("Y").unwrap() {
159 sec_y
160 } else {
161 panic!("Failed to get y")
162 };
163 if *section_y == y {
164 let cloned = section.clone();
165 return Some(cloned);
166 }
167 }
168 None
169 }
170
171 /// Returns the String representation of the biome for a Chunk. Chunks can have different biomes at different vertical sections so use a heightmap to determine the top section if you only want the surface biome.
172 ///
173 /// # Arguments
174 ///
175 /// * `y` - The y section of the chunk to get the biome of.
176 ///
177 /// # Examples
178 ///
179 /// ```rust,no_run
180 /// use simple_anvil::region::Region;
181 /// let region = Region::from_file("r.0.0.mca");
182 /// let chunk = region.get_chunk(0, 0).unwrap();
183 /// let heightmap = chunk.get_heightmap(false);
184 /// let y = if let Some(heights) = heightmap {
185 /// heights.get(0).unwrap()
186 /// } else {
187 /// panic!("Chunk not fully generated");
188 /// }
189 /// let section_y = ((y + 64) / 16 - 4) as i8
190 /// let biome = chunk.get_biome(section_y);
191 /// ```
192 ///
193 /// ```rust,no_run
194 /// use simple_anvil::region::Region;
195 /// let region = Region::from_file("r.0.0.mca");
196 /// let chunk = region.get_chunk(0, 0).unwrap();
197 /// let biome = chunk.get_biome(-3);
198 /// ```
199 pub fn get_biome(&self, y: i32) -> String {
200 let sections = if let Value::List(s) = self.data.get("sections").unwrap() {
201 s
202 } else {
203 panic!("Value should be a list?")
204 };
205 for section in sections {
206 let section = if let Value::Compound(s) = section {
207 s
208 } else {
209 panic!("Should be a compound?")
210 };
211 let current_y = if let Value::Byte(val) = section.get("Y").unwrap() {
212 val
213 } else {
214 panic!("invalid height found")
215 };
216 if current_y == &(((y + 64) / 16 - 4) as i8) {
217 let biomes = if let Value::Compound(c) = section.get("biomes").unwrap() {
218 c
219 } else {
220 panic!("biomes not found")
221 };
222 let pallete = if let Value::List(l) = biomes.get("palette").unwrap() {
223 l
224 } else {
225 panic!("pallete not found")
226 };
227 let biome = if let Value::String(s) = &pallete[0] {
228 s
229 } else {
230 panic!("failed to get string")
231 };
232 return biome.to_string();
233 }
234
235 };
236 return String::from("minecraft:ocean")
237 }
238
239 /// Returns the block at a particular x, y, z coordinate within a chunk. x and z should be the coordinates within the Chunk (0-15).
240 ///
241 /// # Examples
242 ///
243 /// ```rust,no_run
244 /// use simple_anvil::region::Region;
245 /// let region = Region::from_file("r.0.0.mca");
246 /// let chunk = region.get_chunk(0, 0).unwrap();
247 /// let block = chunk.get_block(5, -12, 11);
248 /// println!("{}", block.id);
249 /// ```
250 pub fn get_block(&self, x: i32, mut y: i32, z: i32) -> Block {
251 let section = self.get_section(((y + 64) / 16 - 4) as i8);
252 if section == None {
253 return Block::from_name(String::from("minecraft:air"), Some((self.x as i32 * 32 + x, y, self.z as i32 * 32 + z)), None);
254 }
255 let section = section.unwrap();
256 y = y.rem_euclid(16);
257 let block_states = if let Some(Value::Compound(bs)) = section.get("block_states") {
258 Some(bs)
259 } else {
260 None
261 };
262 if block_states == None {
263 return Block::from_name(String::from("minecraft:air"), Some((self.x as i32 * 32 + x, y, self.z as i32 * 32 + z)), None);
264 }
265
266 let palette = if let Value::List(p) = block_states.unwrap().get("palette").unwrap() {
267 p
268 } else {
269 panic!("Palette should be a list")
270 };
271
272 match block_states {
273 Some(bs) => {
274 let bits = cmp::max(self.bit_length(palette.len() - 1), 4);
275 let index = y * 16 * 16 + z * 16 + x;
276 match bs.get("data") {
277 Some(data) => {
278 let states = if let Value::LongArray(la) = data {
279 la
280 } else {
281 panic!("something here")
282 };
283 let state = index as usize / (64 / bits as usize);
284 let data = states[state];
285 let mut d = 0;
286 let mut modified = false;
287 if data < 0 {
288 d = data as u64;
289 modified = true;
290 }
291 let shifted_data = (if modified { d as usize } else { data as usize }) >> (index as usize % (64 / bits as usize) * bits as usize);
292 let palette_id = shifted_data & (2u32.pow(bits) - 1) as usize;
293 let block = &palette[palette_id];
294 // let props =
295 let props = if let Value::Compound(c) = block {
296 match c.get("Properties") {
297 Some(p_val) => {
298 let properties = if let Value::Compound(p) = p_val {
299 p
300 } else {
301 panic!("Properties should be a compound")
302 };
303 Some(properties.iter().map(|f| (f.0.to_owned(), if let Value::String(s) = f.1 {
304 s.to_owned()
305 } else {
306 panic!("Should be a string?")
307 })).collect::<Vec<_>>())
308
309 },
310 None => None,
311 }
312 } else {
313 panic!("block should be a compound")
314 };
315 return Block::from_palette(block, Some((self.x as i32 * 32 + x, y, self.z as i32 * 32 + z)), props);
316 },
317 None => return Block::from_name(String::from("minecraft:air"), Some((self.x as i32 * 32 + x, y, self.z as i32 * 32 + z)), None)
318 }
319 },
320 None => {
321 return Block::from_name(String::from("minecraft:air"), Some((self.x as i32 * 32 + x, y, self.z as i32 * 32 + z)), None);
322 },
323 }
324
325 }
326
327 /// Returns the bitlength of a usize value
328 fn bit_length(&self, num: usize) -> u32 {
329 // The number of bits that the number consists of, this is an integer and we don't care about signs or leading 0's
330 // 0001 and 1 have the same return value
331 // I think the lowest number that could come in is -1?
332 // usize is always returned from the len function so I think that it will only be usize?
333 if num == 0 {
334 return 0;
335 }
336 // Convert the number to a string version of the binary representation
337 // Get the number of leading 0's
338 let _leading = num.leading_zeros();
339 // Place the number into binary
340 let s_num = format!("{:b}", num);
341 // Remove leading 0's
342 // let s = &s_num[leading as usize..];
343 // Return the length
344 // Leading zeros appear to be removed when changed to bits
345 return s_num.len() as u32;
346 }
347}