Fix clippy warnings: needless_range_loop, collapsible_if, borrowed_box, type_complexity, dead code cleanup

This commit is contained in:
loki5512344 2026-06-15 19:52:21 +02:00
parent fa05187f1c
commit 3d3798136d
22 changed files with 579 additions and 428 deletions

View file

@ -23,8 +23,7 @@ pub type Result<T> = std::result::Result<T, BinaryError>;
/// Save a `World` to a binary file.
pub fn save_binary(world: &World, path: &Path) -> Result<()> {
let bytes = bincode::serialize(world)
.map_err(|e| BinaryError::Serialize(e.to_string()))?;
let bytes = bincode::serialize(world).map_err(|e| BinaryError::Serialize(e.to_string()))?;
std::fs::write(path, &bytes)?;
Ok(())
@ -33,8 +32,8 @@ pub fn save_binary(world: &World, path: &Path) -> Result<()> {
/// Load a `World` from a binary file.
pub fn load_binary(path: &Path) -> Result<World> {
let bytes = std::fs::read(path)?;
let world: World = bincode::deserialize(&bytes)
.map_err(|e| BinaryError::Deserialize(e.to_string()))?;
let world: World =
bincode::deserialize(&bytes).map_err(|e| BinaryError::Deserialize(e.to_string()))?;
Ok(world)
}

View file

@ -51,7 +51,7 @@ pub fn validate_save(path: &Path) -> Result<()> {
let region_count = std::fs::read_dir(&region_dir)
.map_err(|e| ImportError::ValidationFailed(e.to_string()))?
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |ext| ext == "mca"))
.filter(|e| e.path().extension().is_some_and(|ext| ext == "mca"))
.count();
if region_count == 0 {
@ -67,10 +67,8 @@ pub fn validate_save(path: &Path) -> Result<()> {
pub fn import(path: &Path) -> Result<World> {
let path_str = path.display().to_string();
if path.is_dir() {
if path.join("level.dat").exists() && path.join("region").is_dir() {
return import_minecraft_save(path);
}
if path.is_dir() && path.join("level.dat").exists() && path.join("region").is_dir() {
return import_minecraft_save(path);
}
if wp_import::is_world_file(path) {

View file

@ -84,12 +84,12 @@ impl LayerExport for CavesLayerExport {
let data = tile.get_layer_data(LAYER_CAVES)?;
if let LayerBuffer::Nibble(nibbles) = data {
let byte = nibbles[idx / 2];
let value = if idx % 2 == 0 { byte >> 4 } else { byte & 0xF };
if value > 0
&& y < surface_y
&& y >= surface_y - 12
&& y > -60
{
let value = if idx.is_multiple_of(2) {
byte >> 4
} else {
byte & 0xF
};
if value > 0 && y < surface_y && y >= surface_y - 12 && y > -60 {
match block_name {
"minecraft:stone"
| "minecraft:deepslate"
@ -247,7 +247,11 @@ impl LayerExport for ResourcesLayerExport {
let data = tile.get_layer_data(LAYER_RESOURCES)?;
if let LayerBuffer::Nibble(nibbles) = data {
let byte = nibbles[idx / 2];
let value = if idx % 2 == 0 { byte >> 4 } else { byte & 0xF };
let value = if idx.is_multiple_of(2) {
byte >> 4
} else {
byte & 0xF
};
if value > 0 && block_name == "minecraft:stone" && y > -60 {
let ore = match value {
1 => "minecraft:coal_ore",

View file

@ -1,20 +1,23 @@
//! Minecraft world I/O — read and write Java Edition saves.
use std::collections::HashMap;
use log;
use std::collections::{BTreeMap, HashMap};
use std::fs;
use std::path::{Path, PathBuf};
use thiserror::Error;
use log;
use terrafier_fastanvil::io::region::Region;
use terrafier_nbt::io::reader::read_gzip;
use crate::io::layer_export::{LayerExport, apply_layers, biome_name};
use crate::model::dimension::Dimension;
use crate::model::platform::Platform;
use crate::io::layer_export::{apply_layers, biome_name, LayerExport};
use crate::model::tile::Tile;
use crate::model::world::World;
#[allow(clippy::type_complexity)]
type RegionTiles<'a> = BTreeMap<(i32, i32), Vec<(&'a (i32, i32), &'a Tile)>>;
#[derive(Error, Debug)]
pub enum MinecraftIOError {
#[error("IO error: {0}")]
@ -49,14 +52,17 @@ pub fn version_from_data_version(dv: i32) -> Option<Platform> {
max_height: 320,
}),
2866..=2974 => {
log::warn!("DataVersion {} is between 1.18 and 1.19, falling back to 1.18", dv);
log::warn!(
"DataVersion {} is between 1.18 and 1.19, falling back to 1.18",
dv
);
Some(Platform {
id: "java_1_18".into(),
display_name: "Minecraft Java 1.18".into(),
min_height: -64,
max_height: 320,
})
},
}
2975..=3117 => Some(Platform {
id: "java_1_19".into(),
display_name: "Minecraft Java 1.19".into(),
@ -64,14 +70,17 @@ pub fn version_from_data_version(dv: i32) -> Option<Platform> {
max_height: 320,
}),
3118..=3336 => {
log::warn!("DataVersion {} is between 1.19 and 1.20, falling back to 1.19", dv);
log::warn!(
"DataVersion {} is between 1.19 and 1.20, falling back to 1.19",
dv
);
Some(Platform {
id: "java_1_19".into(),
display_name: "Minecraft Java 1.19".into(),
min_height: -64,
max_height: 320,
})
},
}
3337..=3460 => Some(Platform {
id: "java_1_20".into(),
display_name: "Minecraft Java 1.20".into(),
@ -79,14 +88,17 @@ pub fn version_from_data_version(dv: i32) -> Option<Platform> {
max_height: 320,
}),
3461..=3577 => {
log::warn!("DataVersion {} is between 1.20 and 1.20.5, falling back to 1.20", dv);
log::warn!(
"DataVersion {} is between 1.20 and 1.20.5, falling back to 1.20",
dv
);
Some(Platform {
id: "java_1_20".into(),
display_name: "Minecraft Java 1.20 (fallback)".into(),
min_height: -64,
max_height: 320,
})
},
}
3578..=3700 => Some(Platform {
id: "java_1_20_5".into(),
display_name: "Minecraft Java 1.20.5+".into(),
@ -94,14 +106,17 @@ pub fn version_from_data_version(dv: i32) -> Option<Platform> {
max_height: 320,
}),
3701..=3818 => {
log::warn!("DataVersion {} is between 1.20.5 and 1.21, falling back to 1.20.5", dv);
log::warn!(
"DataVersion {} is between 1.20.5 and 1.21, falling back to 1.20.5",
dv
);
Some(Platform {
id: "java_1_20_5".into(),
display_name: "Minecraft Java 1.20.5 (fallback)".into(),
min_height: -64,
max_height: 320,
})
},
}
3819..=3953 => Some(Platform {
id: "java_1_21".into(),
display_name: "Minecraft Java 1.21".into(),
@ -148,104 +163,88 @@ pub fn load_save(path: &Path) -> Result<World> {
for entry in region_entries {
let entry = entry?;
let path = entry.path();
if path.extension().map_or(false, |e| e == "mca") {
if path.extension().is_some_and(|e| e == "mca") {
let file_name = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
let parts: Vec<&str> = file_name.split('.').collect();
if parts.len() >= 3 {
if let (Ok(rx), Ok(rz)) = (parts[1].parse::<i32>(), parts[2].parse::<i32>()) {
let region_bytes = fs::read(&path)?;
let region = Region::from_bytes(rx, rz, &region_bytes)?;
for (local_x, local_z) in region.chunk_coords() {
let chunk_data = region.get_chunk_data(local_x, local_z).unwrap();
if let Ok(chunk_tag) = read_gzip(chunk_data) {
if let Some(chunk) =
terrafier_fastanvil::io::chunk::Chunk::from_nbt(&chunk_tag)
{
let tile_x = chunk.x >> 3;
let tile_z = chunk.z >> 3;
if parts.len() >= 3
&& let (Ok(rx), Ok(rz)) = (parts[1].parse::<i32>(), parts[2].parse::<i32>())
{
let region_bytes = fs::read(&path)?;
let region = Region::from_bytes(rx, rz, &region_bytes)?;
for (local_x, local_z) in region.chunk_coords() {
let chunk_data = region.get_chunk_data(local_x, local_z).unwrap();
if let Ok(chunk_tag) = read_gzip(chunk_data)
&& let Some(chunk) =
terrafier_fastanvil::io::chunk::Chunk::from_nbt(&chunk_tag)
{
let tile_x = chunk.x >> 3;
let tile_z = chunk.z >> 3;
// 8 chunks per tile (128 blocks / 16 blocks per chunk)
let chunk_local_x = (chunk.x & 7) as usize;
let chunk_local_z = (chunk.z & 7) as usize;
// 8 chunks per tile (128 blocks / 16 blocks per chunk)
let chunk_local_x = (chunk.x & 7) as usize;
let chunk_local_z = (chunk.z & 7) as usize;
let tile = tiles
.entry((tile_x, tile_z))
.or_insert_with(|| Tile::new(
tile_x,
tile_z,
platform.min_height,
platform.max_height,
));
let tile = tiles.entry((tile_x, tile_z)).or_insert_with(|| {
Tile::new(tile_x, tile_z, platform.min_height, platform.max_height)
});
for lx in 0..16usize {
for lz in 0..16usize {
let mut surface_y = None;
for lx in 0..16usize {
for lz in 0..16usize {
let mut surface_y = None;
if !chunk.sections.is_empty() {
let mut sorted: Vec<_> =
chunk.sections.iter().collect();
sorted
.sort_by(|a, b| b.section_y.cmp(&a.section_y));
if !chunk.sections.is_empty() {
let mut sorted: Vec<_> = chunk.sections.iter().collect();
sorted.sort_by_key(|b| std::cmp::Reverse(b.section_y));
for section in &sorted {
if section.palette.is_empty() {
continue;
}
let has_blocks =
section.palette.iter().any(|p| {
p.get("Name").map_or(false, |n| {
matches!(
n,
terrafier_nbt::Tag::String(s)
if s != "minecraft:air"
)
})
});
if !has_blocks {
continue;
}
if section.block_data.is_empty() {
if section.palette[0]
.get("Name")
.map_or(false, |n| {
matches!(
n,
terrafier_nbt::Tag::String(s)
if s == "minecraft:air"
)
})
{
continue;
}
surface_y = Some(
(section.section_y as i32) * 16 + 15,
);
break;
}
surface_y = Some(
(section.section_y as i32) * 16 + 15,
);
break;
}
for section in &sorted {
if section.palette.is_empty() {
continue;
}
let tile_local_x = chunk_local_x * 16 + lx;
let tile_local_z = chunk_local_z * 16 + lz;
let has_blocks = section.palette.iter().any(|p| {
p.get("Name").is_some_and(|n| {
matches!(
n,
terrafier_nbt::Tag::String(s)
if s != "minecraft:air"
)
})
});
if tile_local_x < 128 && tile_local_z < 128 {
if let Some(y) = surface_y {
let clamped = (y as i16)
.clamp(tile.min_height, tile.max_height);
tile.heightmap
[tile_local_z * 128 + tile_local_x] = clamped;
}
if !has_blocks {
continue;
}
if section.block_data.is_empty() {
if section.palette[0].get("Name").is_some_and(|n| {
matches!(
n,
terrafier_nbt::Tag::String(s)
if s == "minecraft:air"
)
}) {
continue;
}
surface_y = Some((section.section_y as i32) * 16 + 15);
break;
}
surface_y = Some((section.section_y as i32) * 16 + 15);
break;
}
}
let tile_local_x = chunk_local_x * 16 + lx;
let tile_local_z = chunk_local_z * 16 + lz;
if tile_local_x < 128
&& tile_local_z < 128
&& let Some(y) = surface_y
{
let clamped =
(y as i16).clamp(tile.min_height, tile.max_height);
tile.heightmap[tile_local_z * 128 + tile_local_x] = clamped;
}
}
}
}
@ -272,8 +271,6 @@ pub fn load_save(path: &Path) -> Result<World> {
/// Save a Terrafier World to a Minecraft save directory.
pub fn save_world(world: &World, output_path: &Path) -> Result<()> {
use std::collections::BTreeMap;
fs::create_dir_all(output_path.join("region"))?;
// Write level.dat
@ -287,7 +284,7 @@ pub fn save_world(world: &World, output_path: &Path) -> Result<()> {
};
// Group tiles by region (512×512 blocks = 4×4 tiles of 128×128 each)
let mut regions: BTreeMap<(i32, i32), Vec<(&(i32, i32), &Tile)>> = BTreeMap::new();
let mut regions: RegionTiles = BTreeMap::new();
for (key, tile) in &dim.tiles {
let (tx, tz) = key;
let rx = tx >> 2;
@ -345,7 +342,7 @@ fn pack_indices(indices: &[u16], bits: usize) -> Vec<i64> {
return Vec::new();
}
let total_bits = indices.len() * bits;
let longs = (total_bits + 63) / 64;
let longs = total_bits.div_ceil(64);
let mut data = vec![0i64; longs];
let mask = (1i64 << bits) - 1;
for (i, &idx) in indices.iter().enumerate() {
@ -459,11 +456,14 @@ fn build_chunk_nbt(
let bits = bits_needed(BLOCK_SET.len());
let packed = pack_indices(&indices, bits);
let palette_tags: Vec<terrafier_nbt::Tag> = BLOCK_SET.iter().map(|name| {
let mut entry = HashMap::new();
entry.insert("Name".into(), terrafier_nbt::Tag::String(name.to_string()));
terrafier_nbt::Tag::Compound(entry)
}).collect();
let palette_tags: Vec<terrafier_nbt::Tag> = BLOCK_SET
.iter()
.map(|name| {
let mut entry = HashMap::new();
entry.insert("Name".into(), terrafier_nbt::Tag::String(name.to_string()));
terrafier_nbt::Tag::Compound(entry)
})
.collect();
let mut block_states = HashMap::new();
block_states.insert("palette".into(), terrafier_nbt::Tag::List(palette_tags));
@ -471,7 +471,10 @@ fn build_chunk_nbt(
let biome_palette = vec![{
let mut b = HashMap::new();
b.insert("Name".into(), terrafier_nbt::Tag::String("minecraft:plains".into()));
b.insert(
"Name".into(),
terrafier_nbt::Tag::String("minecraft:plains".into()),
);
terrafier_nbt::Tag::Compound(b)
}];
let mut biomes = HashMap::new();
@ -479,7 +482,10 @@ fn build_chunk_nbt(
let mut sec_compound = HashMap::new();
sec_compound.insert("Y".into(), terrafier_nbt::Tag::Byte(sec_y as i8));
sec_compound.insert("block_states".into(), terrafier_nbt::Tag::Compound(block_states));
sec_compound.insert(
"block_states".into(),
terrafier_nbt::Tag::Compound(block_states),
);
sec_compound.insert("biomes".into(), terrafier_nbt::Tag::Compound(biomes));
sections.push(terrafier_nbt::Tag::Compound(sec_compound));
@ -557,7 +563,15 @@ fn build_chunk_nbt_with_layers(
let surface_y = tile.heightmap[tile_lz * 128 + tile_lx] as i32;
let terrain_id = tile.terrain[tile_lz * 128 + tile_lx];
let base_name = block_name(terrain_id, global_y, surface_y);
let name = apply_layers(tile, tile_lx, tile_lz, global_y, surface_y, base_name, layer_exporters);
let name = apply_layers(
tile,
tile_lx,
tile_lz,
global_y,
surface_y,
base_name,
layer_exporters,
);
BLOCK_SET.iter().position(|s| *s == name).unwrap_or(0) as u16
};
indices.push(idx);
@ -585,20 +599,26 @@ fn build_chunk_nbt_with_layers(
for (pi, name) in used_blocks.iter().enumerate() {
palette_map.insert(name, pi as u16);
}
let remapped: Vec<u16> = indices.iter().map(|&i| {
if i < BLOCK_SET.len() as u16 {
palette_map[BLOCK_SET[i as usize]]
} else {
0
}
}).collect();
let remapped: Vec<u16> = indices
.iter()
.map(|&i| {
if i < BLOCK_SET.len() as u16 {
palette_map[BLOCK_SET[i as usize]]
} else {
0
}
})
.collect();
let packed = pack_indices(&remapped, bits);
let palette_tags: Vec<terrafier_nbt::Tag> = used_blocks.iter().map(|name| {
let mut entry = HashMap::new();
entry.insert("Name".into(), terrafier_nbt::Tag::String(name.to_string()));
terrafier_nbt::Tag::Compound(entry)
}).collect();
let palette_tags: Vec<terrafier_nbt::Tag> = used_blocks
.iter()
.map(|name| {
let mut entry = HashMap::new();
entry.insert("Name".into(), terrafier_nbt::Tag::String(name.to_string()));
terrafier_nbt::Tag::Compound(entry)
})
.collect();
let mut block_states = HashMap::new();
block_states.insert("palette".into(), terrafier_nbt::Tag::List(palette_tags));
@ -615,7 +635,10 @@ fn build_chunk_nbt_with_layers(
let mut sec_compound = HashMap::new();
sec_compound.insert("Y".into(), terrafier_nbt::Tag::Byte(sec_y as i8));
sec_compound.insert("block_states".into(), terrafier_nbt::Tag::Compound(block_states));
sec_compound.insert(
"block_states".into(),
terrafier_nbt::Tag::Compound(block_states),
);
sec_compound.insert("biomes".into(), terrafier_nbt::Tag::Compound(biomes));
sections.push(terrafier_nbt::Tag::Compound(sec_compound));
@ -638,8 +661,6 @@ pub fn save_world_with_layers(
output_path: &Path,
layer_exporters: &[&dyn LayerExport],
) -> Result<()> {
use std::collections::BTreeMap;
fs::create_dir_all(output_path.join("region"))?;
let level_tag = build_level_dat(world)?;
@ -651,7 +672,7 @@ pub fn save_world_with_layers(
None => return Ok(()),
};
let mut regions: BTreeMap<(i32, i32), Vec<(&(i32, i32), &Tile)>> = BTreeMap::new();
let mut regions: RegionTiles = BTreeMap::new();
for (key, tile) in &dim.tiles {
let (tx, tz) = key;
let rx = tx >> 2;
@ -671,7 +692,14 @@ pub fn save_world_with_layers(
let region_local_x = (chunk_x & 31) as u8;
let region_local_z = (chunk_z & 31) as u8;
let chunk_data = build_chunk_nbt_with_layers(chunk_x, chunk_z, tile, chunk_lx, chunk_lz, layer_exporters)?;
let chunk_data = build_chunk_nbt_with_layers(
chunk_x,
chunk_z,
tile,
chunk_lx,
chunk_lz,
layer_exporters,
)?;
if chunk_data.is_empty() {
continue;
@ -746,7 +774,7 @@ pub fn discover_region_files(world_path: &Path) -> Result<Vec<PathBuf>> {
for entry in fs::read_dir(&region_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().map_or(false, |e| e == "mca") {
if path.extension().is_some_and(|e| e == "mca") {
files.push(path);
}
}

View file

@ -53,7 +53,6 @@ const TC_LONGSTRING: u8 = 0x7C;
const TC_PROXYCLASSDESC: u8 = 0x7D;
const TC_ENUM: u8 = 0x7E;
const SC_SERIALIZABLE: u8 = 0x02;
const SC_WRITE_METHOD: u8 = 0x01;
const BASE_WIRE_HANDLE: u32 = 0x7E0000;
@ -75,7 +74,6 @@ const PRIM_BOOL: u8 = b'Z';
struct FieldInfo {
name: String,
type_code: u8,
type_name: Option<String>,
}
#[derive(Debug, Clone)]
@ -94,7 +92,7 @@ enum JvmValue {
ByteArray(Vec<i8>),
ShortArray(Vec<i16>),
IntArray(Vec<i32>),
LongArray(Vec<i64>),
LongArray(()),
Int(i32),
Long(i64),
Skipped,
@ -106,10 +104,10 @@ impl JvmValue {
JvmValue::String(s) => Some(s.clone()),
JvmValue::Object(map) => {
for key in &["name", "key", "id", "value"] {
if let Some(val) = map.get(*key) {
if let JvmValue::String(s) = val {
return Some(s.clone());
}
if let Some(val) = map.get(*key)
&& let JvmValue::String(s) = val
{
return Some(s.clone());
}
}
None
@ -150,12 +148,20 @@ struct JvmStream {
impl JvmStream {
fn new(data: Vec<u8>) -> Self {
JvmStream { data, pos: 0, handles: Vec::new(), class_descs: Vec::new() }
JvmStream {
data,
pos: 0,
handles: Vec::new(),
class_descs: Vec::new(),
}
}
fn read_byte(&mut self) -> std::io::Result<u8> {
if self.pos >= self.data.len() {
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected end of stream"));
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"unexpected end of stream",
));
}
let b = self.data[self.pos];
self.pos += 1;
@ -164,7 +170,10 @@ impl JvmStream {
fn read_bytes(&mut self, n: usize) -> std::io::Result<Vec<u8>> {
if self.pos + n > self.data.len() {
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected end of stream"));
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"unexpected end of stream",
));
}
let slice = self.data[self.pos..self.pos + n].to_vec();
self.pos += n;
@ -173,7 +182,10 @@ impl JvmStream {
fn peek_byte(&self) -> std::io::Result<u8> {
if self.pos >= self.data.len() {
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected end of stream"));
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"unexpected end of stream",
));
}
Ok(self.data[self.pos])
}
@ -201,8 +213,14 @@ impl JvmStream {
let b6 = self.read_byte()? as i64;
let b7 = self.read_byte()? as i64;
let b8 = self.read_byte()? as i64;
Ok((b1 << 56) | (b2 << 48) | (b3 << 40) | (b4 << 32)
| (b5 << 24) | (b6 << 16) | (b7 << 8) | b8)
Ok((b1 << 56)
| (b2 << 48)
| (b3 << 40)
| (b4 << 32)
| (b5 << 24)
| (b6 << 16)
| (b7 << 8)
| b8)
}
fn read_f32(&mut self) -> std::io::Result<f32> {
@ -274,13 +292,11 @@ impl JvmStream {
TC_RESET => {
self.handles.clear();
}
TC_EXCEPTION => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"unexpected TC_EXCEPTION in stream".to_string()));
}
_ => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("unexpected content token: 0x{:02x}", tc)));
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("unexpected content token: 0x{:02x}", tc),
));
}
}
}
@ -336,10 +352,10 @@ impl JvmStream {
self.push_handle(JvmValue::Skipped);
Ok(JvmValue::Skipped)
}
_ => {
Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("expected classdesc token, got 0x{:02x}", tc)))
}
_ => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("expected classdesc token, got 0x{:02x}", tc),
)),
}
}
@ -354,31 +370,26 @@ impl JvmStream {
for _ in 0..field_count {
let type_code = self.read_byte()?;
let name = self.read_utf()?;
let type_name = if type_code == b'[' || type_code == b'L' {
if type_code == b'[' || type_code == b'L' {
let tc = self.read_byte()?;
match tc {
TC_STRING => {
let s = self.read_utf()?;
Some(s)
self.read_utf()?;
}
TC_REFERENCE => {
let handle = self.read_u32_handle();
let idx = (handle - BASE_WIRE_HANDLE) as usize;
if idx < self.handles.len() {
self.handles[idx].as_string()
} else {
None
let _ = self.handles[idx].as_string();
}
}
TC_LONGSTRING => {
Some(self.read_long_utf()?)
self.read_long_utf()?;
}
_ => None,
_ => {}
}
} else {
None
};
fields.push(FieldInfo { name, type_code, type_name });
}
fields.push(FieldInfo { name, type_code });
}
let cd = ClassDesc {
@ -432,8 +443,10 @@ impl JvmStream {
self.handles.clear();
}
other => {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
format!("unexpected token in class annotations: 0x{:02x}", other)));
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("unexpected token in class annotations: 0x{:02x}", other),
));
}
}
}
@ -459,7 +472,7 @@ impl JvmStream {
Some(cd) => cd,
None => {
// Unknown class, skip object data
let handle = self.push_handle(JvmValue::Skipped);
let _handle = self.push_handle(JvmValue::Skipped);
self.skip_object_data(&name)?;
return Ok(JvmValue::Skipped);
}
@ -506,7 +519,7 @@ impl JvmStream {
None
}
fn skip_object_data(&mut self, class_name: &str) -> std::io::Result<()> {
fn skip_object_data(&mut self, _class_name: &str) -> std::io::Result<()> {
// Try to read and discard using class annotations
// This is a best-effort skip
let saved = self.pos;
@ -524,7 +537,7 @@ impl JvmStream {
return self.read_class_annotations();
}
// Try to skip a reasonable amount
let remaining = self.data.len() - self.pos;
let _remaining = self.data.len() - self.pos;
self.pos = self.data.len();
Ok(())
} else {
@ -552,14 +565,14 @@ impl JvmStream {
// For the class hierarchy, read field data from superclasses
// We can find the superclass descriptor from the stored class_descs
let super_name = self.find_super_class_name(&cd.name);
if let Some(super_name) = super_name {
if !super_name.is_empty() && super_name != "java.lang.Object" {
if let Some(super_cd) = self.resolve_class_desc(&super_name) {
let super_fields = self.read_object_fields(&super_cd)?;
if let JvmValue::Object(super_map) = super_fields {
map.extend(super_map);
}
}
if let Some(super_name) = super_name
&& !super_name.is_empty()
&& super_name != "java.lang.Object"
&& let Some(super_cd) = self.resolve_class_desc(&super_name)
{
let super_fields = self.read_object_fields(&super_cd)?;
if let JvmValue::Object(super_map) = super_fields {
map.extend(super_map);
}
}
@ -638,11 +651,10 @@ impl JvmStream {
JvmValue::IntArray(arr)
}
Some(PRIM_LONG) => {
let mut arr = Vec::with_capacity(length);
for _ in 0..length {
arr.push(self.read_i64()?);
let _ = self.read_i64()?;
}
JvmValue::LongArray(arr)
JvmValue::LongArray(())
}
_ => {
// Object array — read elements individually
@ -673,7 +685,9 @@ impl JvmStream {
for _ in 0..size {
let key = self.read_content()?;
let value = self.read_content()?;
let key_str = key.as_string().unwrap_or_else(|| format!("__key_{}", map.len()));
let key_str = key
.as_string()
.unwrap_or_else(|| format!("__key_{}", map.len()));
map.insert(key_str, value);
}
@ -729,16 +743,16 @@ struct WpWorldData {
}
fn extract_world_data(val: &JvmValue) -> Result<WpWorldData> {
let obj = val.as_object()
.ok_or_else(|| WpImportError::InvalidFormat("root object is not a compound object".into()))?;
let obj = val.as_object().ok_or_else(|| {
WpImportError::InvalidFormat("root object is not a compound object".into())
})?;
let name = obj.get("name")
let name = obj
.get("name")
.and_then(|v| v.as_string())
.unwrap_or_else(|| "WorldPainter World".to_string());
let seed = obj.get("seed")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let seed = obj.get("seed").and_then(|v| v.as_u64()).unwrap_or(0);
let mut tiles_data = Vec::new();
@ -761,25 +775,34 @@ fn extract_world_data(val: &JvmValue) -> Result<WpWorldData> {
}
}
Ok(WpWorldData { name, seed, tiles: tiles_data })
Ok(WpWorldData {
name,
seed,
tiles: tiles_data,
})
}
fn collect_tiles(val: &JvmValue, tiles: &mut Vec<WpTileData>) {
match val {
JvmValue::Object(map) => {
// Check if this is a tile by looking for heightMap/terrain fields
if map.contains_key("heightMap") || map.contains_key("terrain") || map.contains_key("waterLevel") {
if let Some(tile) = parse_single_tile(val) {
tiles.push(tile);
return;
}
if (map.contains_key("heightMap")
|| map.contains_key("terrain")
|| map.contains_key("waterLevel"))
&& let Some(tile) = parse_single_tile(val)
{
tiles.push(tile);
return;
}
// Could be a map from tile-key (Long) to Tile
for (_key, sub) in map.iter() {
collect_tiles(sub, tiles);
}
}
JvmValue::ByteArray(_) | JvmValue::ShortArray(_) | JvmValue::IntArray(_) | JvmValue::LongArray(_) => {}
JvmValue::ByteArray(_)
| JvmValue::ShortArray(_)
| JvmValue::IntArray(_)
| JvmValue::LongArray(_) => {}
JvmValue::Skipped => {}
_ => {}
}
@ -794,7 +817,13 @@ fn parse_single_tile(val: &JvmValue) -> Option<WpTileData> {
let terrain = map.get("terrain").and_then(array_to_u8_16384);
let water_level = map.get("waterLevel").and_then(array_to_u8_16384);
Some(WpTileData { x, z, heightmap, terrain, water_level })
Some(WpTileData {
x,
z,
heightmap,
terrain,
water_level,
})
}
fn array_to_heightmap(val: &JvmValue) -> Option<[i16; 16384]> {
@ -884,10 +913,10 @@ impl WpImporter {
/// Check whether a path looks like a WorldPainter .world file.
pub fn is_world_file(path: &Path) -> bool {
path.extension().map_or(false, |ext| ext == "world")
|| path.file_name().map_or(false, |name| {
name.to_string_lossy().ends_with(".world")
})
path.extension().is_some_and(|ext| ext == "world")
|| path
.file_name()
.is_some_and(|name| name.to_string_lossy().ends_with(".world"))
}
/// Import a WorldPainter .world file into a Terrafier World model.
@ -902,7 +931,8 @@ pub fn import_world_file(path: &Path) -> Result<World> {
let data = if raw.len() >= 2 && raw[0] == 0x1F && raw[1] == 0x8B {
let mut decoder = flate2::read::GzDecoder::new(&raw[..]);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)
decoder
.read_to_end(&mut decompressed)
.map_err(|e| WpImportError::Compression(e.to_string()))?;
decompressed
} else {
@ -911,26 +941,31 @@ pub fn import_world_file(path: &Path) -> Result<World> {
// Validate Java serialization magic
if data.len() < 4 {
return Err(WpImportError::InvalidFormat("file too small after decompression".into()));
return Err(WpImportError::InvalidFormat(
"file too small after decompression".into(),
));
}
let magic = u16::from_be_bytes([data[0], data[1]]);
if magic != 0xACED {
return Err(WpImportError::InvalidFormat(
format!("not a Java serialization stream (expected magic ACED, got {:04X})", magic)
));
return Err(WpImportError::InvalidFormat(format!(
"not a Java serialization stream (expected magic ACED, got {:04X})",
magic
)));
}
let stream_version = u16::from_be_bytes([data[2], data[3]]);
if stream_version != 5 {
return Err(WpImportError::InvalidFormat(
format!("unsupported Java serialization stream version: {}", stream_version)
));
return Err(WpImportError::InvalidFormat(format!(
"unsupported Java serialization stream version: {}",
stream_version
)));
}
// Parse the Java serialization stream
let mut stream = JvmStream::new(data);
// Read the stream header content (the first object)
let root = stream.read_content()
let root = stream
.read_content()
.map_err(|e| WpImportError::InvalidFormat(e.to_string()))?;
// Extract world data from parsed stream
@ -945,7 +980,12 @@ pub fn import_world_file(path: &Path) -> Result<World> {
// Build tiles
let mut tiles = std::collections::HashMap::new();
for wp_tile in &world_data.tiles {
let mut tile = Tile::new(wp_tile.x, wp_tile.z, platform.min_height, platform.max_height);
let mut tile = Tile::new(
wp_tile.x,
wp_tile.z,
platform.min_height,
platform.max_height,
);
if let Some(hm) = &wp_tile.heightmap {
tile.heightmap = *hm;

View file

@ -51,10 +51,10 @@ impl<'de> Deserialize<'de> for Dimension {
let dim = Dim::deserialize(d)?;
let mut tiles = HashMap::with_capacity(dim.tiles.len());
for (key, tile) in dim.tiles {
if let Some((tx_s, tz_s)) = key.split_once(',') {
if let (Ok(tx), Ok(tz)) = (tx_s.parse::<i32>(), tz_s.parse::<i32>()) {
tiles.insert((tx, tz), tile);
}
if let Some((tx_s, tz_s)) = key.split_once(',')
&& let (Ok(tx), Ok(tz)) = (tx_s.parse::<i32>(), tz_s.parse::<i32>())
{
tiles.insert((tx, tz), tile);
}
}
Ok(Self {

View file

@ -2,48 +2,96 @@ use super::{DataSize, Layer};
pub struct CavesLayer;
impl Layer for CavesLayer {
fn id(&self) -> &'static str { "caves" }
fn name(&self) -> &'static str { "Caves" }
fn data_size(&self) -> DataSize { DataSize::Nibble }
fn priority(&self) -> i32 { 10 }
fn id(&self) -> &'static str {
"caves"
}
fn name(&self) -> &'static str {
"Caves"
}
fn data_size(&self) -> DataSize {
DataSize::Nibble
}
fn priority(&self) -> i32 {
10
}
}
pub struct RiverLayer;
impl Layer for RiverLayer {
fn id(&self) -> &'static str { "river" }
fn name(&self) -> &'static str { "River" }
fn data_size(&self) -> DataSize { DataSize::Byte }
fn priority(&self) -> i32 { 20 }
fn id(&self) -> &'static str {
"river"
}
fn name(&self) -> &'static str {
"River"
}
fn data_size(&self) -> DataSize {
DataSize::Byte
}
fn priority(&self) -> i32 {
20
}
}
pub struct FrostLayer;
impl Layer for FrostLayer {
fn id(&self) -> &'static str { "frost" }
fn name(&self) -> &'static str { "Frost" }
fn data_size(&self) -> DataSize { DataSize::Bit }
fn priority(&self) -> i32 { 30 }
fn id(&self) -> &'static str {
"frost"
}
fn name(&self) -> &'static str {
"Frost"
}
fn data_size(&self) -> DataSize {
DataSize::Bit
}
fn priority(&self) -> i32 {
30
}
}
pub struct TreesLayer;
impl Layer for TreesLayer {
fn id(&self) -> &'static str { "trees" }
fn name(&self) -> &'static str { "Trees" }
fn data_size(&self) -> DataSize { DataSize::Byte }
fn priority(&self) -> i32 { 40 }
fn id(&self) -> &'static str {
"trees"
}
fn name(&self) -> &'static str {
"Trees"
}
fn data_size(&self) -> DataSize {
DataSize::Byte
}
fn priority(&self) -> i32 {
40
}
}
pub struct BiomeLayer;
impl Layer for BiomeLayer {
fn id(&self) -> &'static str { "biome" }
fn name(&self) -> &'static str { "Biome" }
fn data_size(&self) -> DataSize { DataSize::Byte }
fn priority(&self) -> i32 { 50 }
fn id(&self) -> &'static str {
"biome"
}
fn name(&self) -> &'static str {
"Biome"
}
fn data_size(&self) -> DataSize {
DataSize::Byte
}
fn priority(&self) -> i32 {
50
}
}
pub struct ResourcesLayer;
impl Layer for ResourcesLayer {
fn id(&self) -> &'static str { "resources" }
fn name(&self) -> &'static str { "Resources" }
fn data_size(&self) -> DataSize { DataSize::Nibble }
fn priority(&self) -> i32 { 60 }
fn id(&self) -> &'static str {
"resources"
}
fn name(&self) -> &'static str {
"Resources"
}
fn data_size(&self) -> DataSize {
DataSize::Nibble
}
fn priority(&self) -> i32 {
60
}
}

View file

@ -71,14 +71,20 @@ impl Tile {
}
/// Ensure a layer buffer exists, creating an empty one if needed.
pub fn ensure_layer(&mut self, layer_id: u32, data_size: crate::model::layers::DataSize) -> &mut LayerBuffer {
pub fn ensure_layer(
&mut self,
layer_id: u32,
data_size: crate::model::layers::DataSize,
) -> &mut LayerBuffer {
use crate::model::layers::DataSize;
let total = TILE_SIZE * TILE_SIZE;
self.layer_data.entry(layer_id).or_insert_with(|| match data_size {
DataSize::Bit => LayerBuffer::Bit(vec![0u64; (total + 63) / 64]),
DataSize::Nibble => LayerBuffer::Nibble(vec![0u8; (total + 1) / 2]),
DataSize::Byte => LayerBuffer::Byte(vec![0u8; total]),
DataSize::Int => LayerBuffer::Int(vec![0i32; total]),
})
self.layer_data
.entry(layer_id)
.or_insert_with(|| match data_size {
DataSize::Bit => LayerBuffer::Bit(vec![0u64; total.div_ceil(64)]),
DataSize::Nibble => LayerBuffer::Nibble(vec![0u8; total.div_ceil(2)]),
DataSize::Byte => LayerBuffer::Byte(vec![0u8; total]),
DataSize::Int => LayerBuffer::Int(vec![0i32; total]),
})
}
}

View file

@ -4,7 +4,7 @@ use std::sync::OnceLock;
use crate::model::dimension::Dimension;
use crate::model::terrain::Terrain;
use crate::model::tile::{Tile, TILE_SIZE};
use crate::model::tile::{TILE_SIZE, Tile};
use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation};
/// Determines whether a cell should be modified by an operation.
@ -150,14 +150,15 @@ impl Operation for FilteredOperation {
// Phase 1: snapshot all cells in brush area (if first apply)
let snapshot: Vec<(usize, i16)> = if is_first_apply {
let tile = dim.tiles.get(&(self.tile_x, self.tile_z)).ok_or(
OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
},
)?;
let tile =
dim.tiles
.get(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let r = self.radius as i32;
let cx = self.center_x as i32;
@ -178,24 +179,22 @@ impl Operation for FilteredOperation {
let _ = self.before_snapshot.set(snap.clone());
snap
} else {
self.before_snapshot
.get()
.cloned()
.unwrap_or_default()
self.before_snapshot.get().cloned().unwrap_or_default()
};
// Phase 2: apply inner operation
self.operation.apply(dim)?;
// Phase 3: restore cells that don't pass the filter
let tile = dim.tiles.get_mut(&(self.tile_x, self.tile_z)).ok_or(
OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
},
)?;
let tile =
dim.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
for &(idx, original_h) in &snapshot {
let lx = idx % TILE_SIZE;

View file

@ -67,7 +67,7 @@ impl HeightMapSource for NoiseHeightMap {
let world_z = (tile.z as f64 * tile_size as f64 + lz as f64) * self.scale_z;
let n = noise_gen.get([world_x * self.frequency, world_z * self.frequency]);
let height = (self.base_height + n as f64 * self.amplitude)
let height = (self.base_height + n * self.amplitude)
.round()
.clamp(tile.min_height as f64, tile.max_height as f64)
as i16;
@ -108,16 +108,16 @@ impl HeightMapSource for CombinedHeightMap {
let mut temp_tile = Tile::new(tile.x, tile.z, tile.min_height, tile.max_height);
source.generate(&mut temp_tile, seed.wrapping_add(*weight as u64 * 100));
for i in 0..16384 {
accumulated_heights[i] += temp_tile.heightmap[i] as f64 * weight;
for (i, acc) in accumulated_heights.iter_mut().enumerate() {
*acc += temp_tile.heightmap[i] as f64 * weight;
}
}
let weight_sum: f64 = self.sources.iter().map(|(_, w)| w).sum();
if weight_sum > 0.0 {
for i in 0..16384 {
let h = (accumulated_heights[i] / weight_sum)
for (i, acc) in accumulated_heights.iter().enumerate() {
let h = (*acc / weight_sum)
.round()
.clamp(tile.min_height as f64, tile.max_height as f64)
as i16;

View file

@ -3,8 +3,8 @@
//! Each operation implements the Operation trait and supports undo.
use std::collections::VecDeque;
use std::sync::OnceLock;
use std::sync::Arc;
use std::sync::OnceLock;
use crate::model::dimension::Dimension;
use crate::model::terrain::Terrain;
@ -37,11 +37,7 @@ pub struct HeightOperation {
impl Operation for HeightOperation {
fn name(&self) -> &'static str {
if self.delta >= 0 {
"Raise"
} else {
"Lower"
}
if self.delta >= 0 { "Raise" } else { "Lower" }
}
fn apply(&self, dim: &mut Dimension) -> Result<(), OperationError> {
@ -82,7 +78,7 @@ impl Operation for HeightOperation {
let change = (self.delta as f64 * strength).round() as i16;
let new_height =
(tile.heightmap[idx] as i16 + change).clamp(tile.min_height, tile.max_height);
(tile.heightmap[idx] + change).clamp(tile.min_height, tile.max_height);
tile.heightmap[idx] = new_height;
}
}
@ -95,9 +91,7 @@ impl Operation for HeightOperation {
}
fn inverse(&self) -> Box<dyn Operation> {
let snapshot = self.before_snapshot.get()
.cloned()
.unwrap_or_default();
let snapshot = self.before_snapshot.get().cloned().unwrap_or_default();
Box::new(RestoreHeightsOperation {
tile_x: self.tile_x,
tile_z: self.tile_z,
@ -179,9 +173,7 @@ impl Operation for FlattenOperation {
}
fn inverse(&self) -> Box<dyn Operation> {
let snapshot = self.before_snapshot.get()
.cloned()
.unwrap_or_default();
let snapshot = self.before_snapshot.get().cloned().unwrap_or_default();
Box::new(RestoreHeightsOperation {
tile_x: self.tile_x,
tile_z: self.tile_z,
@ -250,9 +242,7 @@ impl Operation for PaintOperation {
}
fn inverse(&self) -> Box<dyn Operation> {
let snapshot = self.before_snapshot.get()
.cloned()
.unwrap_or_default();
let snapshot = self.before_snapshot.get().cloned().unwrap_or_default();
Box::new(RestoreTerrainOperation {
tile_x: self.tile_x,
tile_z: self.tile_z,
@ -414,8 +404,8 @@ impl Operation for SmoothOperation {
let bz = az + ny;
if bx >= 0 && bx < TILE_SIZE as i32 && bz >= 0 && bz < TILE_SIZE as i32
{
sum +=
tile.heightmap[(bz as usize) * TILE_SIZE + (bx as usize)] as i32;
sum += tile.heightmap[(bz as usize) * TILE_SIZE + (bx as usize)]
as i32;
count += 1;
}
}
@ -463,15 +453,15 @@ impl Operation for ErodeOperation {
}
fn apply(&self, dim: &mut Dimension) -> Result<(), OperationError> {
let tile = dim
.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let tile =
dim.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let r = self.radius as i32;
let cx = self.center_x as i32;
@ -525,8 +515,7 @@ impl Operation for ErodeOperation {
for (ndx, ndz) in [(0, -1), (0, 1), (-1, 0), (1, 0)] {
let bx = ax + ndx;
let bz = az + ndz;
if bx < 0 || bx >= tile_size as i32 || bz < 0 || bz >= tile_size as i32
{
if bx < 0 || bx >= tile_size as i32 || bz < 0 || bz >= tile_size as i32 {
continue;
}
let n_dx = bx - cx;
@ -605,15 +594,15 @@ impl Operation for FillOperation {
}
fn apply(&self, dim: &mut Dimension) -> Result<(), OperationError> {
let tile = dim
.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let tile =
dim.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let cx = self.center_x as usize;
let cz = self.center_z as usize;
@ -697,15 +686,15 @@ impl Operation for FloodOperation {
}
fn apply(&self, dim: &mut Dimension) -> Result<(), OperationError> {
let tile = dim
.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let tile =
dim.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let r = self.radius as i32;
let cx = self.center_x as i32;
@ -765,15 +754,15 @@ impl Operation for PencilOperation {
}
fn apply(&self, dim: &mut Dimension) -> Result<(), OperationError> {
let tile = dim
.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let tile =
dim.tiles
.get_mut(&(self.tile_x, self.tile_z))
.ok_or(OperationError::OutOfBounds {
tx: self.tile_x,
tz: self.tile_z,
x: 0,
z: 0,
})?;
let cx = self.center_x as usize;
let cz = self.center_z as usize;

View file

@ -8,6 +8,12 @@ pub struct PluginRegistry {
pub sources: Vec<Box<dyn TileSourcePlugin>>,
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
@ -52,22 +58,34 @@ impl PluginRegistry {
}
/// Find a layer plugin by name.
pub fn find_layer_by_name(&self, name: &str) -> Option<&Box<dyn LayerPlugin>> {
self.layers.iter().find(|p| p.name() == name)
pub fn find_layer_by_name(&self, name: &str) -> Option<&dyn LayerPlugin> {
self.layers
.iter()
.find(|p| p.name() == name)
.map(Box::as_ref)
}
/// Find an operation plugin by name.
pub fn find_operation_by_name(&self, name: &str) -> Option<&Box<dyn OperationPlugin>> {
self.operations.iter().find(|p| p.name() == name)
pub fn find_operation_by_name(&self, name: &str) -> Option<&dyn OperationPlugin> {
self.operations
.iter()
.find(|p| p.name() == name)
.map(Box::as_ref)
}
/// Find an export plugin by name.
pub fn find_export_by_name(&self, name: &str) -> Option<&Box<dyn ExportPlugin>> {
self.exports.iter().find(|p| p.name() == name)
pub fn find_export_by_name(&self, name: &str) -> Option<&dyn ExportPlugin> {
self.exports
.iter()
.find(|p| p.name() == name)
.map(Box::as_ref)
}
/// Find a tile source plugin by name.
pub fn find_source_by_name(&self, name: &str) -> Option<&Box<dyn TileSourcePlugin>> {
self.sources.iter().find(|p| p.name() == name)
pub fn find_source_by_name(&self, name: &str) -> Option<&dyn TileSourcePlugin> {
self.sources
.iter()
.find(|p| p.name() == name)
.map(Box::as_ref)
}
}

View file

@ -8,9 +8,9 @@ use std::collections::HashMap;
use terrafier_core::io::minecraft::load_save;
use terrafier_fastanvil::io::chunk::Chunk;
use terrafier_fastanvil::io::region::Region;
use terrafier_nbt::Tag;
use terrafier_nbt::io::reader::read_gzip;
use terrafier_nbt::io::writer::to_gzip_bytes;
use terrafier_nbt::Tag;
// ---------------------------------------------------------------------------
// Helpers: synthetic NBT and region file writer
@ -48,10 +48,7 @@ fn make_chunk_nbt_gzip(cx: i32, cz: i32) -> Vec<u8> {
// block_states with a single-entry palette
let mut palette_list: Vec<Tag> = Vec::new();
let mut grass = HashMap::new();
grass.insert(
"Name".into(),
Tag::String("minecraft:grass_block".into()),
);
grass.insert("Name".into(), Tag::String("minecraft:grass_block".into()));
palette_list.push(Tag::Compound(grass));
let mut block_states = HashMap::new();
block_states.insert("palette".into(), Tag::List(palette_list));
@ -164,7 +161,9 @@ fn test_golden_mca_roundtrip() {
let coords = region.chunk_coords();
assert_eq!(coords, vec![(0, 0)]);
let raw = region.get_chunk_data(0, 0).expect("chunk (0,0) should exist");
let raw = region
.get_chunk_data(0, 0)
.expect("chunk (0,0) should exist");
assert!(!raw.is_empty(), "chunk data should not be empty");
// The stored payload is gzip-compressed NBT → decompress via read_gzip
@ -175,11 +174,7 @@ fn test_golden_mca_roundtrip() {
assert_eq!(chunk.x, 0, "chunk xPos");
assert_eq!(chunk.z, 0, "chunk zPos");
assert_eq!(chunk.data_version, 3954, "chunk DataVersion");
assert_eq!(
chunk.status.as_deref(),
Some("full"),
"chunk Status"
);
assert_eq!(chunk.status.as_deref(), Some("full"), "chunk Status");
// Sections
assert_eq!(chunk.sections.len(), 1, "should have 1 section");
@ -211,5 +206,9 @@ fn test_golden_mca_roundtrip() {
// --- 4. Region round-trip via to_bytes / from_bytes ---
let reencoded = region.to_bytes().expect("region should re-serialize");
let region2 = Region::from_bytes(0, 0, &reencoded).expect("re-serialized region should parse");
assert_eq!(region2.chunk_count(), 1, "round-tripped region should still have 1 chunk");
assert_eq!(
region2.chunk_count(),
1,
"round-tripped region should still have 1 chunk"
);
}