use super::{Command, BLOCK_CACHE_SIZE}; use crate::{unwrap_or, opt_unwrap_or}; use crate::spatial::{Vec3, Area, area_rel_block_overlap, area_abs_block_overlap, area_contains_block, area_touches_block}; use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::map_database::MapDatabase; use crate::map_block::{MapBlock, MapBlockError, is_valid_generated, NodeMetadataList, NodeMetadataListExt}; use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map}; use crate::utils::{query_keys, CacheMap}; fn verify_args(args: &InstArgs) -> anyhow::Result<()> { let offset_if_nonzero = args.offset.filter(|&off| off != Vec3::new(0, 0, 0)); if args.invert && offset_if_nonzero.is_some() { anyhow::bail!("Inverted selections cannot be offset."); } Ok(()) } /// Overlay without offsetting anything. /// /// Possible argument configurations: /// - No arguments (copy everything) /// - Area /// - Area + Invert #[inline] fn overlay_no_offset(inst: &mut InstBundle) { let db = &mut inst.db; let idb = inst.idb.as_mut().unwrap(); let invert = inst.args.invert; // Get keys from input database. let keys = query_keys(idb, &inst.status, &[], inst.args.area, invert, true); inst.status.begin_editing(); for key in keys { inst.status.inc_done(); if let Some(area) = inst.args.area { let pos = Vec3::from_block_key(key); if (!invert && area_contains_block(&area, pos)) || (invert && !area_touches_block(&area, pos)) { // If possible, copy whole map block. let data = idb.get_block(key).unwrap(); if is_valid_generated(&data) { db.set_block(key, &data).unwrap(); } } else { // Copy part of map block let res = || -> Result<(), MapBlockError> { let dst_data = opt_unwrap_or!( db.get_block(key).ok() .filter(|d| is_valid_generated(&d)), return Ok(())); let src_data = idb.get_block(key).unwrap(); let mut src_block = MapBlock::deserialize(&src_data)?; let mut dst_block = MapBlock::deserialize(&dst_data)?; let mut src_meta = NodeMetadataList::deserialize( &src_block.metadata.get_ref())?; let mut dst_meta = NodeMetadataList::deserialize( &dst_block.metadata.get_ref())?; let block_part = area_rel_block_overlap(&area, pos) .unwrap(); if invert { // For inverted selections, reverse the order of the // overlay operations. merge_blocks(&dst_block, &mut src_block, block_part, block_part); merge_metadata(&dst_meta, &mut src_meta, block_part, block_part); clean_name_id_map(&mut src_block); db.set_block(key, &src_block.serialize()).unwrap(); } else { merge_blocks(&src_block, &mut dst_block, block_part, block_part); merge_metadata(&src_meta, &mut dst_meta, block_part, block_part); clean_name_id_map(&mut dst_block); db.set_block(key, &dst_block.serialize()).unwrap(); } Ok(()) }(); if res.is_err() { inst.status.inc_failed() } } } else { // No area; copy whole map block. let data = idb.get_block(key).unwrap(); if is_valid_generated(&data) { db.set_block(key, &data).unwrap(); } } } inst.status.end_editing(); } fn get_cached( db: &mut MapDatabase, cache: &mut CacheMap>, key: i64 ) -> Option { match cache.get(&key) { Some(data) => data.clone(), None => { let block = db.get_block(key).ok() .filter(|d| is_valid_generated(d)) .and_then(|d| MapBlock::deserialize(&d).ok()); cache.insert(key, block.clone()); block } } } /// Overlay with offset, with or without area. #[inline] fn overlay_with_offset(inst: &mut InstBundle) { let offset = inst.args.offset.unwrap(); let src_area = inst.args.area; let dst_area = src_area.map(|a| a + offset); let idb = inst.idb.as_mut().unwrap(); // Get keys from output database. let dst_keys = query_keys(&mut inst.db, &inst.status, &[], dst_area, inst.args.invert, true); let mut src_block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE); inst.status.begin_editing(); for dst_key in dst_keys { inst.status.inc_done(); let dst_pos = Vec3::from_block_key(dst_key); let dst_data = opt_unwrap_or!( inst.db.get_block(dst_key).ok().filter(|d| is_valid_generated(d)), continue ); let (mut dst_block, mut dst_meta) = unwrap_or!( || -> Result<_, MapBlockError> { let b = MapBlock::deserialize(&dst_data)?; let m = NodeMetadataList::deserialize(b.metadata.get_ref())?; Ok((b, m)) }(), { inst.status.inc_failed(); continue; } ); let dst_part_abs = dst_area.map_or( Area::new(dst_pos * 16, dst_pos * 16 + 15), |ref a| area_abs_block_overlap(a, dst_pos).unwrap() ); let src_part_abs = dst_part_abs - offset; let src_blocks_needed = src_part_abs.to_touching_block_area(); for src_pos in src_blocks_needed.iterate() { if !src_pos.is_valid_block_pos() { continue; } let src_key = src_pos.to_block_key(); let (src_block, src_meta) = opt_unwrap_or!( || -> Option<_> { let b = get_cached(idb, &mut src_block_cache, src_key)?; let m = NodeMetadataList::deserialize(b.metadata.get_ref()) .ok()?; Some((b, m)) }(), continue ); let src_frag_abs = area_abs_block_overlap(&src_part_abs, src_pos) .unwrap(); let src_frag_rel = src_frag_abs - src_pos * 16; let dst_frag_rel = area_rel_block_overlap( &(src_frag_abs + offset), dst_pos).unwrap(); merge_blocks(&src_block, &mut dst_block, src_frag_rel, dst_frag_rel); merge_metadata(&src_meta, &mut dst_meta, src_frag_rel, dst_frag_rel); } clean_name_id_map(&mut dst_block); *dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version); inst.db.set_block(dst_key, &dst_block.serialize()).unwrap(); } inst.status.end_editing(); } fn overlay(inst: &mut InstBundle) { let offset = inst.args.offset.unwrap_or(Vec3::new(0, 0, 0)); if offset == Vec3::new(0, 0, 0) { overlay_no_offset(inst); } else { overlay_with_offset(inst); } } pub fn get_command() -> Command { Command { func: overlay, verify_args: Some(verify_args), args: vec![ (ArgType::InputMapPath, "Path to input map file"), (ArgType::Area(false), "Area to overlay"), (ArgType::Invert, "Overlay all nodes outside the given area"), (ArgType::Offset(false), "Vector to offset nodes by"), ], help: "Copy part or all of one world/map into another." } }