/* mc_schem is a rust library to generate, load, manipulate and save minecraft schematic files. Copyright (C) 2024 joseph This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ use std::collections::HashMap; use std::env; use std::fs::{create_dir_all, File}; // use std::io::Read; use fastnbt::Value; use flate2::{Compression, GzBuilder}; use flate2::read::{GzDecoder}; use ndarray::Array3; use rand::Rng; use mc_schem::block::CommonBlock; use mc_schem::{Schematic, WorldEdit12LoadOption, LitematicaLoadOption, LitematicaSaveOption, Block, schem, old_block, DataVersion, WorldEdit13SaveOption, Region, BlockEntity, MetaDataIR, WorldEdit13LoadOption}; #[test] fn block_id_parse() { let pass_ids = [ "minecraft:air", "minecraft:stone", "minecraft:birch_log[axis=x]", "hay_block[axis=y]", "mod_name:stained_glass[color=white]", "minecraft:stone_slab[half=top,variant=brick]", "minecraft:red_mushroom_block[east=true,west=true,north=true,south=true,up=true,down=true]", "mushroom_stem[east=true,west=true,north=true,south=true,up=true,down=true]", ]; for id in pass_ids { let blk_res = Block::from_id(id); match blk_res { Err(err) => { panic!("{} failed with {}", id, err); } Ok(blk) => { println!( "namespace = {}, id = {}, attributes:", blk.namespace, blk.id ); for att in &blk.attributes { println!("\t {} : {}", att.0, att.1); } let _ = blk.full_id(); } } } let nopass_ids=[ "minecraft::air", "minecraft:stone]", "minecraft:birch_log[", "jjj[axis=y]hay_block", "mod_name:[color=white]stained_glass", "stained_glass[mod_name:color=white]", "minecraft:stone_slab[half=top,,variant=brick]", "minecraft:red_mushroom_block[east=true,west=true,north=true,south=true,up=true,down=true,]", "mushroom_stem[east=true,west=true,north=true,south=true,up=true,down=true###]" ]; for id in nopass_ids { let blk_res = Block::from_id(id); match blk_res { Err(err) => { println!("\"{}\" is invalid because {}", id, err); } Ok(blk) => { panic!( "Invalid id \"{}\" is parsed successfully, result: \"{}\"", id, blk.full_id() ); } } } } #[test] fn block_bits_required() { for palette_size in 0..258 { let bits = schem::litematica::block_required_bits(palette_size); println!("{} blocks requires {} bit(s)", palette_size, bits); } } #[test] fn ceil_up_to() { let tests = [ ((0isize, 12isize), 0isize), ((13, 12), 24), ((120, 12), 120), ((121, 12), 132) ]; for ((a, b), expected) in tests { let result = schem::common::ceil_up_to(a, b); if result != expected { panic!("{} ceil up to {} should b {}, but found {}", a, b, expected, result); } else { println!("{} ceil up to {} = {}", a, b, expected); } } } #[test] fn litematica_local_bit_index_to_global_bit_index() { let lbi_list = [63, 0, -1, -64, -65, -128, -129, -192]; let expected = [63, 0, 127, 64, 191, 128, 255, 192]; for idx in 0..lbi_list.len() { let lbi = lbi_list[idx]; let computed = schem::litematica::MultiBitSet::logic_bit_index_to_global_bit_index(lbi); if computed != expected[idx] { panic!("logical bit index {} should be mapped to {}, but found {}", lbi, expected[idx], computed); } } } #[test] fn litematica_multi_bit_set_read() { let data = [14242959524133701664u64, 1244691354]; let mbs = schem::litematica::MultiBitSet::from_data(&data, 18, 5).unwrap(); assert_eq!(mbs.basic_mask(), 0b11111); for idx in 0..mbs.len() { println!("mbs[{}] = {}", idx, mbs.get(idx)); } } #[test] fn litematica_multi_bit_set_rw() { //use rand::Rng; let mut rng = rand::thread_rng(); let bits = 1..65; let num_elements = 1 << 10; for element_bits in bits { let mut mbs = schem::litematica::MultiBitSet::new(); mbs.reset(element_bits, num_elements); let value_mask = mbs.element_max_value(); let mut values: Vec = Vec::with_capacity(num_elements); for _ in 0..num_elements { values.push(rng.gen::() & value_mask); } for (idx, val) in values.iter().enumerate() { mbs.set(idx, *val).unwrap(); } for (idx, val) in values.iter().enumerate() { let found = mbs.get(idx); if found != *val { panic!("mbs[{}] should be {}, but found {}", idx, val, found); } } } } #[test] fn litematica_3d_array_decode() { use crate::schem::{LitematicaLoadOption}; println!("Current dir: {}", env::current_dir().unwrap().to_string_lossy()); let src_filename = "./test_files/litematica/test01.litematic"; let schem = Schematic::from_litematica_file(src_filename, &LitematicaLoadOption::default()).unwrap().0; for y in 0..19 { let bid = schem.first_block_index_at([0, y, 0]).unwrap(); if bid != y as u16 { panic!("Block index at [0, {}, 0] should be {}, but found {}", y, y, bid); } } } // generate value for VALID_DAMAGE_LUT in old_blocks.rs #[test] fn process_mc12_damage_data() { let raw_data = [ "0", "0-6", "0", "0-2", "0", "0-5", "0-5,8-13", "0", "0-15", "0-15", "0-15", "0-15", "0-1", "0", "0", "0", "0", "0-15", "0-15", "0-1", "0", "0", "0", "0-5,8-13", "0-2", "0", "0-3,8-15", "0-5,8-13", "0-5,8-13", "0-5,8-13", "0", "0-2", "0", "0-5,8-13", "0-5,8-13", "0-15", "0-5,8-13", "0", "0-8", "0", "0", "0", "0", "0-15", "0-15", "0", "0-1", "0", "0", "0", "1-5", "0-15", "0", "0-7", "2-5", "0-15", "0", "0", "0", "0-7", "0-7", "2-5", "2-5", "0-15", "0-11", "2-5", "0-9", "0-7", "2-5", "0-15", "0-1", "0-11", "0-1", "0", "0", "1-5", "1-5", "0-5,8-13", "0-7", "0", "0", "0-15", "0", "0-15", "0-1", "0", "0-3", "0", "0", "0", "1-2", "0-3", "0-6", "0-15", "0-15", "0-15", "0-15", "0-5", "0-3", "0-10,14-15", "0-10,14-15", "0", "0", "0", "0-7", "0-7", "0-15", "0-15", "0-7", "0-7", "0", "0", "0", "0", "0-7", "0-3", "0", "0-7", "0-3", "0", "0-7", "0", "0", "0", "0", "0-5", "0-5,8-13", "0-11", "0-7", "0", "2-5", "0-15", "0-1,4-5,8-9,12-13", "0", "0-7", "0-7", "0-7", "0-5,8-13", "0", "0-1", "0-15", "0-7", "0-7", "0-5,8-13", "0-5,8-13", "0-11", "2-5", "0-15", "0-15", "0-15", "0-15", "0-15", "0", "0", "0,2-5,8,10-13", "0-4", "0-7", "0-5,8-13", "0-5,8-13", "0-15", "0-15", "0-1,4-5,8-9,12-13", "0-1,4-5,8-9,12-13", "0-7", "0-7", "0", "0", "0-15", "0-2", "0", "0,4,8", "0-15", "0", "0", "0", "0-5,8-11", "0-15", "2-5", "0-15", "0-2", "0-7", "0,8", "0,8", "0-15", "0-15", "0-15", "0-15", "0-15", "0", "0", "0", "0", "0", "0-11", "0-11", "0-11", "0-11", "0-11", "0-5", "0", "0-5", "0", "0,4,8", "0-7", "0", "0,8", "0", "0-3", "0", "0", "0-5,8-13", "0-5,8-13", "0-3", "0", "0", "0", "0,4,8", "0", "0-5,8-13", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-5", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-3", "0-15", "0-15", "", "", "0-3" ]; //let mut bit_represented = [0u16; 256]; fn parse_raw_data(raw: &str) -> Option { if raw.is_empty() { return Some(0); } let mut result: u16 = 0; for part in raw.split(',') { if !part.contains('-') { let parsed; match str::parse::(part) { Ok(p) => { parsed = p; } Err(_) => return None, } assert!(parsed <= 15); result |= (1u16) << parsed; } else { let splitted = part.split('-'); // check if valid if splitted.clone().count() != 2 { return None; } let mut first_str: &str = ""; let mut last_str: &str = ""; for (idx, s) in splitted.enumerate() { if idx == 0 { first_str = s; } if idx == 1 { last_str = s; } } // get begin and end let first: u16; match str::parse::(first_str) { Ok(n) => first = n, Err(_) => return None, } let last: u16; match str::parse::(last_str) { Ok(n) => last = n, Err(_) => return None, } assert!(first <= last); assert!(last <= 15); for n in first..(last + 1) { result |= (1u16) << n; } } } return Some(result); } print!("bit_represented = ["); for idx in 0..256 { let bit_represented = parse_raw_data(raw_data[idx]).unwrap(); print!("{:#016b}, ", bit_represented); } print!("];"); } #[test] fn test_old_block_number_id_damage() { let mut valid_damages = Vec::with_capacity(16); let skip_id = [253, 254]; for id in 0..256 { let id = id as u8; if skip_id.contains(&id) { continue; } old_block::get_valid_damage_values(id, &mut valid_damages); assert_ne!(valid_damages.len(), 0); for damage in &valid_damages { let _ = Block::from_old(id, *damage, DataVersion::Java_1_12_2).unwrap(); } } } #[test] fn parse_full_blocks_mc12() { let num_id_array; { let decoder = GzDecoder::new(File::open("./test_files/schematic/full-blocks-1.12.2.schematic").unwrap()); let nbt = fastnbt::from_reader(decoder).unwrap(); num_id_array = Schematic::parse_number_id_from_we12(&nbt).unwrap(); } let litematic = Schematic::from_litematica_file( "./test_files/litematica/full-blocks-1.12.2.litematic", &LitematicaLoadOption::default()).unwrap().0; let lite_region = &litematic.regions[0]; for dim in 0..3 { assert_eq!(num_id_array.shape()[dim], lite_region.shape_yzx()[dim] as usize); } let mut hash: HashMap<(u8, u8), String> = HashMap::new(); hash.reserve(256 * 16); for y in 0..num_id_array.shape()[0] { for z in 0..num_id_array.shape()[1] { for x in 0..num_id_array.shape()[2] { let (id, damage) = num_id_array[[y, z, x]]; if hash.contains_key(&(id, damage)) { continue; } let block = litematic.first_block_at([x as i32, y as i32, z as i32]).unwrap(); hash.insert((id, damage), block.full_id()); } } } println!(" id \t damage \t string id"); for ((id, damage), val) in &hash { println!("{id}\t{damage}\t{val}"); } let mut damage_list = [0u16; 256]; for ((id, damage), _) in &hash { assert!(*damage < 16); damage_list[*id as usize] |= 1u16 << damage; } println!("\n\n\n id \t damage"); for (id, damages) in damage_list.iter().enumerate() { let string = damage_list_u16_to_string(*damages); println!("{id}\t{string}"); } fn damage_list_u16_to_string(damage_list: u16) -> String { if damage_list == 0 { return "".to_string(); } let mut result = String::new(); let mut temp = Vec::with_capacity(32); let mut prev: Option = None; for damage in 0..16 { if damage_list & (1u16 << damage) == 0 { continue; } if let Some(prev_damage) = prev { if damage - prev_damage > 1 { temp.push(255); } } prev = Some(damage); temp.push(damage); } for slice in temp.split(|x| *x == 255) { assert!(slice.len() > 0); if slice.len() == 1 { result.push_str(&slice.first().unwrap().to_string()); } else { result.push_str(&format!("{}-{}", slice.first().unwrap(), slice.last().unwrap())); } result.push(','); } if !result.is_empty() { result.pop(); } return result; } } #[test] fn make_mc12_numeric_lut() { let schem_file = "./test_files/schematic/full-blocks-1.12.2.schematic"; let schem_option = WorldEdit12LoadOption::default(); let (schem, _, num_id_array) = Schematic::from_world_edit_12_file(schem_file, &schem_option).unwrap(); let lite = Schematic::from_litematica_file( "./test_files/litematica/full-blocks-1.12.2.litematic", &LitematicaLoadOption::default()).unwrap().0; for dim in 0..3 { let nia_shape_xyz = Region::pos_yzx_to_xyz(&[num_id_array.shape()[0], num_id_array.shape()[1], num_id_array.shape()[2]]); assert_eq!(nia_shape_xyz[dim], schem.shape()[dim] as usize); assert_eq!(schem.shape()[dim], lite.shape()[dim]); } let shape = lite.shape(); let mut hash: HashMap>> = HashMap::new(); hash.reserve(256); for y in 0..shape[1] as usize { for z in 0..shape[2] as usize { for x in 0..shape[0] as usize { let (id, damage) = num_id_array[[y, z, x]]; let pos = [x as i32, y as i32, z as i32]; let full_id = lite.first_block_at(pos).unwrap().full_id(); let be_tags = match schem.first_block_entity_at(pos) { Some(be) => be.tags.clone(), None => HashMap::new(), }; let hash = hash.entry(id.to_string()).or_insert(HashMap::new()); let hash = hash.entry(damage.to_string()).or_insert(HashMap::new()); hash.insert(full_id, Value::Compound(be_tags)); } } } create_dir_all("./target/test/make_mc12_numeric_lut").unwrap(); let file = File::create("./target/test/make_mc12_numeric_lut/out.nbt").unwrap(); let encoder = GzBuilder::new().filename("out.nbt").write(file, Compression::best()); fastnbt::to_writer(encoder, &hash).unwrap(); } #[test] fn load_save_vanilla_structure() { use schem::{VanillaStructureLoadOption, VanillaStructureSaveOption}; let schem = Schematic::from_vanilla_structure_file( "./test_files/vanilla_structure/test01.nbt", &VanillaStructureLoadOption::default()).unwrap().0; create_dir_all("./target/test/load_save_vanilla_structure").unwrap(); schem.save_vanilla_structure_file( "./target/test/load_save_vanilla_structure/out01.nbt", &VanillaStructureSaveOption::default()).expect("Failed to save vanilla structure file"); Schematic::from_vanilla_structure_file( "./target/test/load_save_vanilla_structure/out01.nbt", &VanillaStructureLoadOption::default()) .expect("Failed to load saved vanilla structure"); } #[test] fn load_save_litematica() { use schem::{LitematicaLoadOption}; //println!("Current dir: {}", env::current_dir().unwrap().to_string_lossy()); let src_dir = "./test_files/litematica"; let out_dir = "./target/test/load_save_litematica"; create_dir_all(out_dir).unwrap(); for id in 1..4 { let src_filename = format!("{}/test{:02}.litematic", src_dir, id); let dst_filename = format!("{}/out{:02}.litematic", out_dir, id); let schem = Schematic::from_litematica_file(&src_filename, &LitematicaLoadOption::default()).unwrap().0; schem.save_litematica_file(&dst_filename, &LitematicaSaveOption::default()).expect("Failed to save litematica file"); Schematic::from_litematica_file(&dst_filename, &LitematicaLoadOption::default()).expect("Failed to load saved litematica file"); //println!("Metadata: \n{:?}", schem.metadata_litematica()); } } #[test] fn load_litematica_with_negative_size() { let src_dir = "./test_files/litematica"; let src_filename = format!("{}/negative-size-Supercharged_contained_shulker_farm.litematic", src_dir); let _ = Schematic::from_litematica_file(&src_filename, &LitematicaLoadOption::default()).unwrap().0; } #[test] fn load_save_world_edit13() { use schem::WorldEdit13LoadOption; let src_dir = "./test_files/schem"; let out_dir = "./target/test/load_save_world_edit13"; create_dir_all(out_dir).unwrap(); for id in 1..3 { let src_filename = format!("{}/test{:02}.schem", src_dir, id); let dst_filename = format!("{}/out{:02}.schem", out_dir, id); let schem = Schematic::from_world_edit_13_file(&src_filename, &WorldEdit13LoadOption::default()).unwrap().0; schem.save_world_edit_13_file(&dst_filename, &WorldEdit13SaveOption::default()).expect("Failed to save .schem file"); Schematic::from_world_edit_13_file(&dst_filename, &WorldEdit13LoadOption::default()).expect("Failed to load saved .schem file"); //println!("Metadata: \n{:?}", schem.metadata_litematica()); } } #[test] fn load_save_world_edit12() { use schem::WorldEdit12LoadOption; //let src_dir = "./test_files/schematic"; let out_dir = "./target/test/load_save_world_edit12"; create_dir_all(out_dir).unwrap(); let _ = Schematic::from_world_edit_12_file("./test_files/schematic/full-blocks-1.12.2.schematic", &WorldEdit12LoadOption::default()).unwrap(); } #[test] fn make_test_litematic() { let mut commands = Vec::with_capacity(16 * 16 * 16); for id in 0..256 { let x = (id / 16) * 2; let z = (id % 16) * 2; let str_id = old_block::OLD_BLOCK_ID[id as usize]; for damage in 0..16 { let y = damage * 2; commands.push(format!("execute @p ~ ~ ~ setblock ~{x} ~{y} ~{z} {str_id} {damage} replace")); } } assert_eq!(commands.len(), 16 * 16 * 16); let schem_shape = [64, 1, 64]; let mut schem = Schematic::new(); { let mut region = Region::new(); region.reshape(&schem_shape); region.fill_with(&Block::air()); region.name = "main".to_string(); let blk_first = Block::from_id("command_block[conditional=false,facing=east]").unwrap(); let blk_x_positive = Block::from_id("chain_command_block[conditional=false,facing=east]").unwrap(); let blk_x_negative = Block::from_id("chain_command_block[conditional=false,facing=west]").unwrap(); let blk_z_positive = Block::from_id("chain_command_block[conditional=false,facing=south]").unwrap(); let mut command_block_nbt = HashMap::new(); command_block_nbt.insert("conditionMet".to_string(), Value::Byte(0)); command_block_nbt.insert("auto".to_string(), Value::Byte(1));//always active command_block_nbt.insert("CustomName".to_string(), Value::String("@".to_string())); command_block_nbt.insert("id".to_string(), Value::String("minecraft:command_block".to_string())); command_block_nbt.insert("SuccessCount".to_string(), Value::Int(0)); command_block_nbt.insert("TrackOutput".to_string(), Value::Byte(1)); command_block_nbt.insert("UpdateLastExecution".to_string(), Value::Byte(1)); let mut counter = 0; for z in 0..64 { for x_offset in 0..64 { let is_first_block = (x_offset == 0) && (z == 0); let x = if z % 2 == 0 { x_offset } else { 63 - x_offset }; let cur_blk: &Block = if is_first_block { &blk_first } else if x_offset == 63 { &blk_z_positive } else if z % 2 == 0 { &blk_x_positive } else { &blk_x_negative }; region.set_block([x, 0, z], cur_blk).expect("Failed to set block"); command_block_nbt.insert("Command".to_string(), Value::String(commands[counter].to_string())); let mut be = BlockEntity::new(); *(command_block_nbt.get_mut("auto").unwrap()) = Value::Byte(if is_first_block { 0 } else { 1 }); be.tags = command_block_nbt.clone(); region.set_block_entity_at([x, 0, z], be); counter += 1; } } schem.regions.push(region); } { let mut md = MetaDataIR::default(); md.mc_data_version = DataVersion::Java_1_12_2 as i32; schem.metadata = md; } create_dir_all("./target/test/make_test_litematic").unwrap(); schem.save_litematica_file("./target/test/make_test_litematic/out.litematic", &LitematicaSaveOption::default()).unwrap(); } #[test] fn correct_test_litematica() { let pos_block = [ ([0, 0, 0], "white_concrete"), ([10, 0, 0], "light_gray_concrete"), ([0, 10, 0], "gray_concrete"), ([0, 0, 10], "black_concrete"), ([10, 10, 0], "brown_concrete"), ([10, 0, 10], "red_concrete"), ([0, 10, 10], "orange_concrete"), ([10, 10, 10], "yellow_concrete"), ]; let schem = Schematic::from_litematica_file( "./test_files/litematica/correct_test.litematic", &LitematicaLoadOption::default()).unwrap().0; for x in 0..schem.shape()[0] { for y in 0..schem.shape()[1] { for z in 0..schem.shape()[2] { let blk = schem.first_block_at([x, y, z]).unwrap(); if blk.is_air() { continue; } println!("[{x}, {y}, {z}] => {}", blk.id); } } } for (pos, id) in pos_block { let parsed_id = schem.first_block_at(pos).unwrap(); assert_eq!(parsed_id.id, id); } } #[test] #[allow(unused_assignments)] fn correct_test_mc13_plus() { let test_versions = ["1.14.4", "1.18.2", "1.19.4", "1.20.2", ];//, let mut err_counter = 0; for ver in test_versions { let litematica_file = format!("./test_files/litematica/full-blocks-{ver}.litematic"); let schem_file = format!("./test_files/schem/full-blocks-{ver}.schem"); let schem = Schematic::from_world_edit_13_file(&schem_file, &WorldEdit13LoadOption::default()).unwrap().0; let lite = Schematic::from_litematica_file(&litematica_file, &LitematicaLoadOption::default()).unwrap().0; assert_eq!(lite.shape(), schem.shape()); for x in 0..lite.shape()[0] { for y in 0..lite.shape()[1] { for z in 0..lite.shape()[2] { let pos = [x, y, z]; let blk_l = lite.first_block_at(pos).unwrap(); let blk_s = schem.first_block_at(pos).unwrap(); if blk_l != blk_s { err_counter += 1; let id_l: u16 = lite.first_block_index_at(pos).unwrap(); let id_s: u16 = schem.first_block_index_at(pos).unwrap(); panic!("In {ver}, block at [{x}, {y}, {z}] is different: \n litematica => {}, id= {id_l}\n schem => {}, id = {id_s}", blk_l, blk_s); } } } } } println!("err_counter = {err_counter}"); assert_eq!(err_counter, 0); } #[test] #[allow(unused_assignments)] fn correct_test_mc12() { let test_versions = ["1.12.2"];//, let mut err_counter = 0; let soft_check_ids = ["grass", "dirt", "bed", "piston_head", "fire", "oak_stairs", "wooden_door", "stone_stairs", "iron_door", "unpowered_repeater", "powered_repeater", "fence_gate", "double_plant", "spruce_fence_gate", "birch_fence_gate", "jungle_fence_gate", "dark_oak_fence_gate", "acacia_fence_gate", "spruce_door", "birch_door", "jungle_door", "acacia_door", "dark_oak_door"]; for ver in test_versions { let litematica_file = format!("./test_files/litematica/full-blocks-{ver}.litematic"); let schem; let schem_file = format!("./test_files/schematic/full-blocks-{ver}.schematic"); schem = Schematic::from_world_edit_12_file(&schem_file, &WorldEdit12LoadOption::default()).unwrap().0; let lite = Schematic::from_litematica_file(&litematica_file, &LitematicaLoadOption::default()).unwrap().0; let mut ok_counter = 0; assert_eq!(lite.shape(), schem.shape()); for x in 0..lite.shape()[0] { for y in 0..lite.shape()[1] { for z in 0..lite.shape()[2] { let pos = [x, y, z]; let blk_l = lite.first_block_at(pos).unwrap(); let blk_s = schem.first_block_at(pos).unwrap(); if blk_l != blk_s { if blk_l.is_inherited_from(blk_l) && soft_check_ids.contains(&blk_l.id.as_str()) { continue; } err_counter += 1; let id_l: u16 = lite.first_block_index_at(pos).unwrap(); let id_s: u16 = schem.first_block_index_at(pos).unwrap(); panic!("In {ver}, block at [{x}, {y}, {z}] is different: \n litematica => {}, id= {id_l}\n schem => {}, id = {id_s}", blk_l, blk_s); } ok_counter += 1; } } } println!("ok_counter = {ok_counter}"); } println!("err_counter = {}", err_counter); assert_eq!(err_counter, 0); } #[test] fn test_merge_regions() { let mut schem = Schematic::from_litematica_file("./test_files/litematica/multi-region01.litematic", &LitematicaLoadOption::default()).unwrap().0; schem.merge_regions(&CommonBlock::Air.to_block()); create_dir_all("./target/test/test_merge_regions").unwrap(); let out_file = "./target/test/test_merge_regions/multi-region01-out.litematic"; schem.save_litematica_file(out_file, &LitematicaSaveOption::default()).unwrap() } #[test] fn test_3d_array_order() { let mut arr: ndarray::Array3 = Array3::zeros([2, 3, 4]); { let mut i = 0; for y in 0..arr.shape()[0] { for z in 0..arr.shape()[1] { for x in 0..arr.shape()[2] { arr[[y, z, x]] = i; i += 1; } } } } for i in arr { print!("{}, ", i); } } // #[test] // fn check_mca() { // let filename = "F:\\Users\\Joseph\\Documents\\Games\\Minecraft\\PCL2\\.minecraft\\versions\\1.20.2-Fabric 0.15.6\\saves\\New World\\region\\r.0.0.mca"; // // let file_len = std::fs::metadata(filename).unwrap().len(); // let mut src = File::open(filename).unwrap(); // // let mut segments: Vec<[u8; 4096]> = vec![]; // segments.reserve((file_len / 4096) as usize); // // loop { // let mut seg = [0u8; 4096]; // let len = src.read(&mut seg).unwrap(); // if len == 0 { // break; // } // if len != 4096 { // panic!("Incomplete segment, expected 4096 bytes, but only {len} bytes"); // } // segments.push(seg); // } // // fn parse_segment(segments: &[[u8; 4096]]) -> Result, fastnbt::error::Error> { // let mut bytes: &[u8]; // unsafe { // bytes = &*slice_from_raw_parts(segments.as_ptr() as *const u8, segments.len() * 4096); // } // // let data_bytes; // { // let mut len_be: [u8; 4] = [0; 4]; // let temp = bytes.read(&mut len_be).unwrap(); // debug_assert!(temp == 4); // data_bytes = u32::from_be_bytes(len_be); // } // let compress_byte: u8; // { // let mut b: [u8; 1] = [0]; // let temp = bytes.read(&mut b).unwrap(); // debug_assert!(temp == 1); // compress_byte = b[0]; // assert!(compress_byte >= 1); // assert!(compress_byte <= 3); // } // { // assert_eq!(compress_byte, 2);//zlib // let decoder = ZlibDecoder::new(bytes); // fastnbt::from_reader(decoder) // } // } // // let nbt = parse_segment(&segments[2..3]).unwrap(); // for (key, _) in &nbt { // println!("\t{key}"); // } // }