From 3d3798136db5d073a9e3e9f418f89134cd97fa19 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 15 Jun 2026 19:52:21 +0200 Subject: [PATCH] Fix clippy warnings: needless_range_loop, collapsible_if, borrowed_box, type_complexity, dead code cleanup --- bench/benches/nbt.rs | 2 +- bench/benches/render.rs | 2 +- bench/benches/world.rs | 6 +- core/src/io/binary.rs | 7 +- core/src/io/import.rs | 8 +- core/src/io/layer_export.rs | 18 +- core/src/io/minecraft.rs | 280 ++++++++++++++----------- core/src/io/wp_import.rs | 200 +++++++++++------- core/src/model/dimension.rs | 8 +- core/src/model/layers/builtin.rs | 96 ++++++--- core/src/model/tile.rs | 20 +- core/src/ops/filters.rs | 41 ++-- core/src/ops/heightmap.rs | 10 +- core/src/ops/operations.rs | 101 ++++----- core/src/plugins/registry.rs | 34 ++- core/tests/golden_test.rs | 23 +- crates/fastanvil/src/io/region.rs | 25 ++- crates/palette-compress/src/palette.rs | 6 + crates/palette-compress/src/section.rs | 6 + gui/src/app.rs | 32 ++- gui/src/tools.rs | 8 +- gui/src/view.rs | 74 ++++--- 22 files changed, 579 insertions(+), 428 deletions(-) diff --git a/bench/benches/nbt.rs b/bench/benches/nbt.rs index d2e2de7..d9dcd78 100755 --- a/bench/benches/nbt.rs +++ b/bench/benches/nbt.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use terrafier_nbt::tag::Tag; use terrafier_nbt::{reader, writer}; diff --git a/bench/benches/render.rs b/bench/benches/render.rs index e658723..a25c568 100755 --- a/bench/benches/render.rs +++ b/bench/benches/render.rs @@ -1,4 +1,4 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use terrafier_core::io::export::render_to_image; use terrafier_core::model::tile::Tile; diff --git a/bench/benches/world.rs b/bench/benches/world.rs index c31f6f9..0dadbbf 100755 --- a/bench/benches/world.rs +++ b/bench/benches/world.rs @@ -1,14 +1,12 @@ use std::sync::Arc; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, black_box, criterion_group, criterion_main}; use terrafier_core::io::export::render_to_image; use terrafier_core::model::brush::SymmetricBrush; use terrafier_core::model::terrain::Terrain; use terrafier_core::model::world::World; -use terrafier_core::ops::operations::{ - HeightOperation, Operation, PaintOperation, -}; +use terrafier_core::ops::operations::{HeightOperation, Operation, PaintOperation}; fn bench_world_new(c: &mut Criterion) { c.bench_function("world/new_default", |b| { diff --git a/core/src/io/binary.rs b/core/src/io/binary.rs index 76b3dc3..fa5148a 100755 --- a/core/src/io/binary.rs +++ b/core/src/io/binary.rs @@ -23,8 +23,7 @@ pub type Result = std::result::Result; /// 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 { 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) } diff --git a/core/src/io/import.rs b/core/src/io/import.rs index b0c0d6f..a0ced3c 100755 --- a/core/src/io/import.rs +++ b/core/src/io/import.rs @@ -51,7 +51,7 @@ pub fn validate_save(path: &Path) -> Result<()> { let region_count = std::fs::read_dir(®ion_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 { 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) { diff --git a/core/src/io/layer_export.rs b/core/src/io/layer_export.rs index a339cf7..0279b7c 100644 --- a/core/src/io/layer_export.rs +++ b/core/src/io/layer_export.rs @@ -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", diff --git a/core/src/io/minecraft.rs b/core/src/io/minecraft.rs index 14f5069..008b6de 100755 --- a/core/src/io/minecraft.rs +++ b/core/src/io/minecraft.rs @@ -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 { 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 { 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 { 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 { 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 { 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::(), parts[2].parse::()) { - let region_bytes = fs::read(&path)?; - let region = Region::from_bytes(rx, rz, ®ion_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::(), parts[2].parse::()) + { + let region_bytes = fs::read(&path)?; + let region = Region::from_bytes(rx, rz, ®ion_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 { /// 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 { 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 = 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 = 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 = indices.iter().map(|&i| { - if i < BLOCK_SET.len() as u16 { - palette_map[BLOCK_SET[i as usize]] - } else { - 0 - } - }).collect(); + let remapped: Vec = 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 = 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 = 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> { for entry in fs::read_dir(®ion_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); } } diff --git a/core/src/io/wp_import.rs b/core/src/io/wp_import.rs index 98df06d..e1b914f 100644 --- a/core/src/io/wp_import.rs +++ b/core/src/io/wp_import.rs @@ -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, } #[derive(Debug, Clone)] @@ -94,7 +92,7 @@ enum JvmValue { ByteArray(Vec), ShortArray(Vec), IntArray(Vec), - LongArray(Vec), + 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) -> 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 { 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> { 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 { 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 { @@ -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 { - 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 { } } - Ok(WpWorldData { name, seed, tiles: tiles_data }) + Ok(WpWorldData { + name, + seed, + tiles: tiles_data, + }) } fn collect_tiles(val: &JvmValue, tiles: &mut Vec) { 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 { 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 { 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 { // 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 { // 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; diff --git a/core/src/model/dimension.rs b/core/src/model/dimension.rs index 804c396..cbe14d9 100755 --- a/core/src/model/dimension.rs +++ b/core/src/model/dimension.rs @@ -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::(), tz_s.parse::()) { - tiles.insert((tx, tz), tile); - } + if let Some((tx_s, tz_s)) = key.split_once(',') + && let (Ok(tx), Ok(tz)) = (tx_s.parse::(), tz_s.parse::()) + { + tiles.insert((tx, tz), tile); } } Ok(Self { diff --git a/core/src/model/layers/builtin.rs b/core/src/model/layers/builtin.rs index 7391ed1..4c4aa44 100644 --- a/core/src/model/layers/builtin.rs +++ b/core/src/model/layers/builtin.rs @@ -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 + } } diff --git a/core/src/model/tile.rs b/core/src/model/tile.rs index fd8b1e0..ac44f1a 100755 --- a/core/src/model/tile.rs +++ b/core/src/model/tile.rs @@ -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]), + }) } } diff --git a/core/src/ops/filters.rs b/core/src/ops/filters.rs index 055cc88..7d762c4 100644 --- a/core/src/ops/filters.rs +++ b/core/src/ops/filters.rs @@ -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; diff --git a/core/src/ops/heightmap.rs b/core/src/ops/heightmap.rs index c938f86..9f5be0f 100755 --- a/core/src/ops/heightmap.rs +++ b/core/src/ops/heightmap.rs @@ -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; diff --git a/core/src/ops/operations.rs b/core/src/ops/operations.rs index 5786111..bd05c26 100755 --- a/core/src/ops/operations.rs +++ b/core/src/ops/operations.rs @@ -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 { - 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 { - 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 { - 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; diff --git a/core/src/plugins/registry.rs b/core/src/plugins/registry.rs index 060473b..bc12b85 100755 --- a/core/src/plugins/registry.rs +++ b/core/src/plugins/registry.rs @@ -8,6 +8,12 @@ pub struct PluginRegistry { pub sources: Vec>, } +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> { - 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> { - 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> { - 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> { - 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) } } diff --git a/core/tests/golden_test.rs b/core/tests/golden_test.rs index ff4e75a..13127bc 100755 --- a/core/tests/golden_test.rs +++ b/core/tests/golden_test.rs @@ -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 { // block_states with a single-entry palette let mut palette_list: Vec = 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" + ); } diff --git a/crates/fastanvil/src/io/region.rs b/crates/fastanvil/src/io/region.rs index a8b8377..eb003e8 100755 --- a/crates/fastanvil/src/io/region.rs +++ b/crates/fastanvil/src/io/region.rs @@ -46,17 +46,17 @@ impl Region { let mut timestamps = [0u32; 1024]; // Read location table (first 4096 bytes: 1024 entries x 4 bytes) - for i in 0..1024 { + for loc in locations.iter_mut() { let mut buf = [0u8; 4]; reader.read_exact(&mut buf)?; - locations[i] = u32::from_be_bytes(buf); + *loc = u32::from_be_bytes(buf); } // Read timestamp table (second 4096 bytes: 1024 entries x 4 bytes) - for i in 0..1024 { + for ts in timestamps.iter_mut() { let mut buf = [0u8; 4]; reader.read_exact(&mut buf)?; - timestamps[i] = u32::from_be_bytes(buf); + *ts = u32::from_be_bytes(buf); } let mut chunks = HashMap::new(); @@ -111,7 +111,7 @@ impl Region { (local_x, local_z), ChunkEntry { offset: sector_offset, - size: sector_count as u32, + size: sector_count, timestamp, data: Some(decompressed), }, @@ -140,12 +140,15 @@ impl Region { /// Set chunk data at local coordinates (0..32, 0..32). /// `data` should be decompressed NBT bytes. pub fn set_chunk_data(&mut self, local_x: u8, local_z: u8, data: Vec) { - self.chunks.insert((local_x, local_z), ChunkEntry { - offset: 0, - size: 0, - timestamp: 0, - data: Some(data), - }); + self.chunks.insert( + (local_x, local_z), + ChunkEntry { + offset: 0, + size: 0, + timestamp: 0, + data: Some(data), + }, + ); } /// List all chunk coordinates present in this region. diff --git a/crates/palette-compress/src/palette.rs b/crates/palette-compress/src/palette.rs index 7ff4f44..9329f35 100755 --- a/crates/palette-compress/src/palette.rs +++ b/crates/palette-compress/src/palette.rs @@ -84,6 +84,12 @@ pub struct BlockPalette { index_map: HashMap, } +impl Default for BlockPalette { + fn default() -> Self { + Self::new() + } +} + impl BlockPalette { pub fn new() -> Self { BlockPalette { diff --git a/crates/palette-compress/src/section.rs b/crates/palette-compress/src/section.rs index 2ab88b2..455735b 100755 --- a/crates/palette-compress/src/section.rs +++ b/crates/palette-compress/src/section.rs @@ -10,6 +10,12 @@ pub struct SectionData { pub storage: BitArray, } +impl Default for SectionData { + fn default() -> Self { + Self::new() + } +} + impl SectionData { /// Create a new section filled with air. pub fn new() -> Self { diff --git a/gui/src/app.rs b/gui/src/app.rs index 790d464..29b5d80 100755 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -1,8 +1,8 @@ use std::collections::VecDeque; +use terrafier_core::World; use terrafier_core::model::terrain::Terrain; use terrafier_core::ops::operations::Operation; -use terrafier_core::World; #[derive(Clone, Copy, PartialEq)] pub enum ToolMode { @@ -41,8 +41,6 @@ pub struct TerrafierApp { pub brush_local_x: Option, pub brush_local_z: Option, pub show_heightmap: bool, - pub zoom: f32, - pub view_offset: (f32, f32), pub show_new_world: bool, pub show_export: bool, pub status_message: String, @@ -67,8 +65,6 @@ impl TerrafierApp { brush_local_x: None, brush_local_z: None, show_heightmap: false, - zoom: 1.0, - view_offset: (0.0, 0.0), show_new_world: false, show_export: false, status_message: "Ready".to_string(), @@ -129,19 +125,19 @@ impl eframe::App for TerrafierApp { if ui.button("New World").clicked() { self.show_new_world = true; } - if ui.button("Open").clicked() { - if let Some(path) = rfd::FileDialog::new().pick_folder() { - match terrafier_core::io::import::import(&path) { - Ok(world) => { - self.world = Some(world); - self.selected_tile = None; - self.undo_stack.clear(); - self.redo_stack.clear(); - self.status_message = format!("Opened world from {}", path.display()); - } - Err(e) => { - self.status_message = format!("Open error: {}", e); - } + if ui.button("Open").clicked() + && let Some(path) = rfd::FileDialog::new().pick_folder() + { + match terrafier_core::io::import::import(&path) { + Ok(world) => { + self.world = Some(world); + self.selected_tile = None; + self.undo_stack.clear(); + self.redo_stack.clear(); + self.status_message = format!("Opened world from {}", path.display()); + } + Err(e) => { + self.status_message = format!("Open error: {}", e); } } } diff --git a/gui/src/tools.rs b/gui/src/tools.rs index 18ec361..5ccc732 100755 --- a/gui/src/tools.rs +++ b/gui/src/tools.rs @@ -1,12 +1,12 @@ use std::sync::Arc; +use terrafier_core::Terrain; use terrafier_core::model::brush::SymmetricBrush; use terrafier_core::model::tile::TILE_SIZE; use terrafier_core::ops::operations::{ FlattenOperation, HeightOperation, MultiTileOperation, Operation, PaintOperation, SmoothOperation, }; -use terrafier_core::Terrain; use crate::app::{TerrafierApp, ToolMode}; @@ -103,8 +103,10 @@ fn apply_tool(app: &mut TerrafierApp) { for otx in min_tx..=max_tx { for otz in min_tz..=max_tz { - let local_cx = (global_cx - otx * TILE_SIZE as i32).clamp(0, TILE_SIZE as i32 - 1) as u32; - let local_cz = (global_cz - otz * TILE_SIZE as i32).clamp(0, TILE_SIZE as i32 - 1) as u32; + let local_cx = + (global_cx - otx * TILE_SIZE as i32).clamp(0, TILE_SIZE as i32 - 1) as u32; + let local_cz = + (global_cz - otz * TILE_SIZE as i32).clamp(0, TILE_SIZE as i32 - 1) as u32; if dim.tiles.contains_key(&(otx, otz)) { let brush = Arc::new(SymmetricBrush::new(app.brush_radius as f64)); diff --git a/gui/src/view.rs b/gui/src/view.rs index 5c617b3..899d816 100755 --- a/gui/src/view.rs +++ b/gui/src/view.rs @@ -107,27 +107,34 @@ pub fn show_viewport(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { ); // Draw selection border - if let Some((sel_tx, sel_tz)) = app.selected_tile { - if sel_tx >= min_tx && sel_tx <= max_tx && sel_tz >= min_tz && sel_tz <= max_tz { - let bx = rect.min.x + (sel_tx - min_tx) as f32 * display_size as f32; - let bz = rect.min.y + (sel_tz - min_tz) as f32 * display_size as f32; - let border_rect = - egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(display_size as f32)); - painter.rect_stroke( - border_rect, - 0.0, - egui::Stroke::new(3.0, Color32::WHITE), - egui::StrokeKind::Middle, - ); - } + if let Some((sel_tx, sel_tz)) = app.selected_tile + && sel_tx >= min_tx + && sel_tx <= max_tx + && sel_tz >= min_tz + && sel_tz <= max_tz + { + let bx = rect.min.x + (sel_tx - min_tx) as f32 * display_size as f32; + let bz = rect.min.y + (sel_tz - min_tz) as f32 * display_size as f32; + let border_rect = + egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(display_size as f32)); + painter.rect_stroke( + border_rect, + 0.0, + egui::Stroke::new(3.0, Color32::WHITE), + egui::StrokeKind::Middle, + ); } // Draw brush position marker - if let (Some((sel_tx, sel_tz)), Some(bx), Some(bz)) = (app.selected_tile, app.brush_local_x, app.brush_local_z) { + if let (Some((sel_tx, sel_tz)), Some(bx), Some(bz)) = + (app.selected_tile, app.brush_local_x, app.brush_local_z) + { let tile_x_in_pixels = (sel_tx - min_tx) as f32 * display_size as f32; let tile_z_in_pixels = (sel_tz - min_tz) as f32 * display_size as f32; - let brush_x = rect.min.x + tile_x_in_pixels + (bx as f32 * display_size as f32 / TILE_SIZE as f32); - let brush_z = rect.min.y + tile_z_in_pixels + (bz as f32 * display_size as f32 / TILE_SIZE as f32); + let brush_x = + rect.min.x + tile_x_in_pixels + (bx as f32 * display_size as f32 / TILE_SIZE as f32); + let brush_z = + rect.min.y + tile_z_in_pixels + (bz as f32 * display_size as f32 / TILE_SIZE as f32); let brush_radius_px = app.brush_radius as f32 * display_size as f32 / TILE_SIZE as f32; painter.circle_stroke( egui::pos2(brush_x, brush_z), @@ -137,21 +144,26 @@ pub fn show_viewport(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) { } // Handle click to select tile - if response.clicked() { - if let Some(pos) = response.interact_pointer_pos() { - let lx = pos.x - rect.min.x; - let lz = pos.y - rect.min.y; - if lx >= 0.0 && lz >= 0.0 { - let tx = (lx / display_size as f32).floor() as i32 + min_tx; - let tz = (lz / display_size as f32).floor() as i32 + min_tz; - if dim.tiles.contains_key(&(tx, tz)) { - app.selected_tile = Some((tx, tz)); - let local_x = ((lx as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127); - let local_z = ((lz as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127); - app.brush_local_x = Some(local_x); - app.brush_local_z = Some(local_z); - app.status_message = format!("Selected tile ({}, {}) at local ({}, {})", tx, tz, local_x, local_z); - } + if response.clicked() + && let Some(pos) = response.interact_pointer_pos() + { + let lx = pos.x - rect.min.x; + let lz = pos.y - rect.min.y; + if lx >= 0.0 && lz >= 0.0 { + let tx = (lx / display_size as f32).floor() as i32 + min_tx; + let tz = (lz / display_size as f32).floor() as i32 + min_tz; + if dim.tiles.contains_key(&(tx, tz)) { + app.selected_tile = Some((tx, tz)); + let local_x = + ((lx as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127); + let local_z = + ((lz as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127); + app.brush_local_x = Some(local_x); + app.brush_local_z = Some(local_z); + app.status_message = format!( + "Selected tile ({}, {}) at local ({}, {})", + tx, tz, local_x, local_z + ); } } }