From bc41838671b5b8d2537bb42e732d6917473c19dd Mon Sep 17 00:00:00 2001 From: Wieland <29458682+wielandb@users.noreply.github.com> Date: Sun, 25 Jan 2026 23:57:20 +0100 Subject: [PATCH] Resolve merge conflicts --- src/block_definitions.rs | 38 ++++++ src/element_processing/amenities.rs | 31 +++-- src/world_editor/java.rs | 177 +++++++++++++++------------- 3 files changed, 147 insertions(+), 99 deletions(-) diff --git a/src/block_definitions.rs b/src/block_definitions.rs index 6903304..08a84b2 100644 --- a/src/block_definitions.rs +++ b/src/block_definitions.rs @@ -267,6 +267,14 @@ impl Block { 186 => "polished_andesite_stairs", 187 => "nether_brick_stairs", 188 => "barrel", + 189 => "fern", + 190 => "cobweb", + 191 => "chiseled_bookshelf", + 192 => "chiseled_bookshelf", + 193 => "chiseled_bookshelf", + 194 => "chiseled_bookshelf", + 195 => "chipped_anvil", + 196 => "damaged_anvil", _ => panic!("Invalid id"), } } @@ -464,6 +472,26 @@ impl Block { map.insert("half".to_string(), Value::String("top".to_string())); map })), + 191 => Some(Value::Compound({ + let mut map = HashMap::new(); + map.insert("facing".to_string(), Value::String("north".to_string())); + map + })), + 192 => Some(Value::Compound({ + let mut map = HashMap::new(); + map.insert("facing".to_string(), Value::String("east".to_string())); + map + })), + 193 => Some(Value::Compound({ + let mut map = HashMap::new(); + map.insert("facing".to_string(), Value::String("south".to_string())); + map + })), + 194 => Some(Value::Compound({ + let mut map = HashMap::new(); + map.insert("facing".to_string(), Value::String("west".to_string())); + map + })), _ => None, } } @@ -699,6 +727,16 @@ pub const QUARTZ_STAIRS: Block = Block::new(185); pub const POLISHED_ANDESITE_STAIRS: Block = Block::new(186); pub const NETHER_BRICK_STAIRS: Block = Block::new(187); pub const BARREL: Block = Block::new(188); +pub const FERN: Block = Block::new(189); +pub const COBWEB: Block = Block::new(190); +pub const CHISELLED_BOOKSHELF_NORTH: Block = Block::new(191); +pub const CHISELLED_BOOKSHELF_EAST: Block = Block::new(192); +pub const CHISELLED_BOOKSHELF_SOUTH: Block = Block::new(193); +pub const CHISELLED_BOOKSHELF_WEST: Block = Block::new(194); +// Backwards-compatible alias (defaults to north-facing) +pub const CHISELLED_BOOKSHELF: Block = CHISELLED_BOOKSHELF_NORTH; +pub const CHIPPED_ANVIL: Block = Block::new(195); +pub const DAMAGED_ANVIL: Block = Block::new(196); /// Maps a block to its corresponding stair variant #[inline] diff --git a/src/element_processing/amenities.rs b/src/element_processing/amenities.rs index c3650fc..11c3273 100644 --- a/src/element_processing/amenities.rs +++ b/src/element_processing/amenities.rs @@ -2,11 +2,11 @@ use crate::args::Args; use crate::block_definitions::*; use crate::bresenham::bresenham_line; use crate::coordinate_system::cartesian::XZPoint; -use crate::floodfill::flood_fill_area; +use crate::floodfill::flood_fill_area; // Needed for inline amenity flood fills use crate::osm_parser::ProcessedElement; use crate::world_editor::WorldEditor; use fastnbt::Value; -use rand::{seq::SliceRandom, Rng}; +use rand::{seq::SliceRandom, Rng, SeedableRng}; use std::collections::{HashMap, HashSet}; pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement, args: &Args) { @@ -88,18 +88,15 @@ pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement, let ground_block: Block = OAK_PLANKS; let roof_block: Block = STONE_BLOCK_SLAB; - let polygon_coords: Vec<(i32, i32)> = element - .nodes() - .map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z)) - .collect(); - - if polygon_coords.is_empty() { - return; - } - + let polygon_coords: Vec<(i32, i32)> = + element.nodes().map(|node| (node.x, node.z)).collect(); let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, args.timeout.as_ref()); + if floor_area.is_empty() { + return; + } + // Fill the floor area for (x, z) in floor_area.iter() { editor.set_block(ground_block, *x, 0, *z, None, None); @@ -126,8 +123,10 @@ pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement, "bench" => { // Place a bench if let Some(pt) = first_node { - // 50% chance to 90 degrees rotate the bench using if - if rand::random::() { + // Use a deterministic RNG for consistent bench orientation across region boundaries. + let mut rng = rand::rngs::StdRng::seed_from_u64(element.id()); + // 50% chance to 90 degrees rotate the bench + if rng.gen_bool(0.5) { editor.set_block(SMOOTH_STONE, pt.x, 1, pt.z, None, None); editor.set_block(OAK_LOG, pt.x + 1, 1, pt.z, None, None); editor.set_block(OAK_LOG, pt.x - 1, 1, pt.z, None, None); @@ -141,10 +140,8 @@ pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement, "shelter" => { let roof_block: Block = STONE_BRICK_SLAB; - let polygon_coords: Vec<(i32, i32)> = element - .nodes() - .map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z)) - .collect(); + let polygon_coords: Vec<(i32, i32)> = + element.nodes().map(|node| (node.x, node.z)).collect(); let roof_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, args.timeout.as_ref()); diff --git a/src/world_editor/java.rs b/src/world_editor/java.rs index 738f5e4..ddafc26 100644 --- a/src/world_editor/java.rs +++ b/src/world_editor/java.rs @@ -23,9 +23,11 @@ use crate::telemetry::{send_log, LogLevel}; impl<'a> WorldEditor<'a> { /// Creates a region file for the given region coordinates. pub(super) fn create_region(&self, region_x: i32, region_z: i32) -> Region { - let out_path = self - .world_dir - .join(format!("region/r.{}.{}.mca", region_x, region_z)); + let region_dir = self.world_dir.join("region"); + let out_path = region_dir.join(format!("r.{}.{}.mca", region_x, region_z)); + + // Ensure region directory exists before creating region files + std::fs::create_dir_all(®ion_dir).expect("Failed to create region directory"); const REGION_TEMPLATE: &[u8] = include_bytes!("../../assets/minecraft/region.template"); @@ -75,6 +77,8 @@ impl<'a> WorldEditor<'a> { } /// Saves the world in Java Edition Anvil format. + /// + /// Uses parallel processing with rayon for fast region saving. pub(super) fn save_java(&mut self) { println!("{} Saving world...", "[7/7]".bold()); emit_gui_progress_update(90.0, "Saving world..."); @@ -104,89 +108,12 @@ impl<'a> WorldEditor<'a> { .regions .par_iter() .for_each(|((region_x, region_z), region_to_modify)| { - let mut region = self.create_region(*region_x, *region_z); - let mut ser_buffer = Vec::with_capacity(8192); - - for (&(chunk_x, chunk_z), chunk_to_modify) in ®ion_to_modify.chunks { - if !chunk_to_modify.sections.is_empty() || !chunk_to_modify.other.is_empty() { - // Read existing chunk data if it exists - let existing_data = region - .read_chunk(chunk_x as usize, chunk_z as usize) - .unwrap() - .unwrap_or_default(); - - // Parse existing chunk or create new one - let mut chunk: Chunk = if !existing_data.is_empty() { - fastnbt::from_bytes(&existing_data).unwrap() - } else { - Chunk { - sections: Vec::new(), - x_pos: chunk_x + (region_x * 32), - z_pos: chunk_z + (region_z * 32), - is_light_on: 0, - other: FnvHashMap::default(), - } - }; - - // Update sections while preserving existing data - let new_sections: Vec
= chunk_to_modify.sections().collect(); - for new_section in new_sections { - if let Some(existing_section) = - chunk.sections.iter_mut().find(|s| s.y == new_section.y) - { - // Merge block states - existing_section.block_states.palette = - new_section.block_states.palette; - existing_section.block_states.data = new_section.block_states.data; - } else { - // Add new section if it doesn't exist - chunk.sections.push(new_section); - } - } - - // Preserve existing block entities and merge with new ones - merge_compound_list(&mut chunk, chunk_to_modify, "block_entities"); - merge_compound_list(&mut chunk, chunk_to_modify, "entities"); - - // Update chunk coordinates and flags - chunk.x_pos = chunk_x + (region_x * 32); - chunk.z_pos = chunk_z + (region_z * 32); - - // Create Level wrapper and save - let level_data = create_level_wrapper(&chunk); - ser_buffer.clear(); - fastnbt::to_writer(&mut ser_buffer, &level_data).unwrap(); - region - .write_chunk(chunk_x as usize, chunk_z as usize, &ser_buffer) - .unwrap(); - } - } - - // Second pass: ensure all chunks exist - for chunk_x in 0..32 { - for chunk_z in 0..32 { - let abs_chunk_x = chunk_x + (region_x * 32); - let abs_chunk_z = chunk_z + (region_z * 32); - - // Check if chunk exists in our modifications - let chunk_exists = - region_to_modify.chunks.contains_key(&(chunk_x, chunk_z)); - - // If chunk doesn't exist, create it with base layer - if !chunk_exists { - let (ser_buffer, _) = Self::create_base_chunk(abs_chunk_x, abs_chunk_z); - region - .write_chunk(chunk_x as usize, chunk_z as usize, &ser_buffer) - .unwrap(); - } - } - } + self.save_single_region(*region_x, *region_z, region_to_modify); // Update progress let regions_done = regions_processed.fetch_add(1, Ordering::SeqCst) + 1; - // Update progress at regular intervals (every ~1% or at least every 10 regions) - // This ensures progress is visible even with many regions + // Update progress at regular intervals (every ~10% or at least every 10 regions) let update_interval = (total_regions / 10).max(1); if regions_done.is_multiple_of(update_interval) || regions_done == total_regions { let progress = 90.0 + (regions_done as f64 / total_regions as f64) * 9.0; @@ -198,6 +125,92 @@ impl<'a> WorldEditor<'a> { save_pb.finish(); } + + /// Saves a single region to disk. + /// + /// This is extracted to allow streaming mode to save and release regions one at a time. + fn save_single_region( + &self, + region_x: i32, + region_z: i32, + region_to_modify: &super::common::RegionToModify, + ) { + let mut region = self.create_region(region_x, region_z); + let mut ser_buffer = Vec::with_capacity(8192); + + for (&(chunk_x, chunk_z), chunk_to_modify) in ®ion_to_modify.chunks { + if !chunk_to_modify.sections.is_empty() || !chunk_to_modify.other.is_empty() { + // Read existing chunk data if it exists + let existing_data = region + .read_chunk(chunk_x as usize, chunk_z as usize) + .unwrap() + .unwrap_or_default(); + + // Parse existing chunk or create new one + let mut chunk: Chunk = if !existing_data.is_empty() { + fastnbt::from_bytes(&existing_data).unwrap() + } else { + Chunk { + sections: Vec::new(), + x_pos: chunk_x + (region_x * 32), + z_pos: chunk_z + (region_z * 32), + is_light_on: 0, + other: FnvHashMap::default(), + } + }; + + // Update sections while preserving existing data + let new_sections: Vec
= chunk_to_modify.sections().collect(); + for new_section in new_sections { + if let Some(existing_section) = + chunk.sections.iter_mut().find(|s| s.y == new_section.y) + { + // Merge block states + existing_section.block_states.palette = new_section.block_states.palette; + existing_section.block_states.data = new_section.block_states.data; + } else { + // Add new section if it doesn't exist + chunk.sections.push(new_section); + } + } + + // Preserve existing block entities and entities and merge with new ones + merge_compound_list(&mut chunk, chunk_to_modify, "block_entities"); + merge_compound_list(&mut chunk, chunk_to_modify, "entities"); + + // Update chunk coordinates and flags + chunk.x_pos = chunk_x + (region_x * 32); + chunk.z_pos = chunk_z + (region_z * 32); + + // Create Level wrapper and save + let level_data = create_level_wrapper(&chunk); + ser_buffer.clear(); + fastnbt::to_writer(&mut ser_buffer, &level_data).unwrap(); + region + .write_chunk(chunk_x as usize, chunk_z as usize, &ser_buffer) + .unwrap(); + } + } + + // Second pass: ensure all chunks exist + for chunk_x in 0..32 { + for chunk_z in 0..32 { + let abs_chunk_x = chunk_x + (region_x * 32); + let abs_chunk_z = chunk_z + (region_z * 32); + + // Check if chunk exists in our modifications + let chunk_exists = region_to_modify.chunks.contains_key(&(chunk_x, chunk_z)); + + // If chunk doesn't exist, create it with base layer + if !chunk_exists { + let (ser_buffer, _) = Self::create_base_chunk(abs_chunk_x, abs_chunk_z); + region + .write_chunk(chunk_x as usize, chunk_z as usize, &ser_buffer) + .unwrap(); + } + } + } + } } /// Helper function to get entity coordinates