use super::Command; use crate::spatial::{Vec3, Area, area_contains_block, area_touches_block, area_rel_block_overlap}; use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::map_block::MapBlock; use crate::time_keeper::TimeKeeper; use crate::utils::{query_keys, to_bytes, fmt_big_num}; fn do_replace( block: &mut MapBlock, key: i64, search_id: u16, new_node: &[u8], area: Option, invert: bool, tk: &mut TimeKeeper ) -> u64 { let block_pos = Vec3::from_block_key(key); let mut count = 0; // Replace nodes in a portion of a map block. if area.is_some() && area_contains_block(&area.unwrap(), block_pos) != area_touches_block(&area.unwrap(), block_pos) { let _t = tk.get_timer("replace (partial block)"); let node_area = area_rel_block_overlap(&area.unwrap(), block_pos) .unwrap(); let mut new_replace_id = false; let replace_id = block.nimap.get_id(new_node) .unwrap_or_else(|| { new_replace_id = true; block.nimap.get_max_id().unwrap() + 1 }); let mut idx = 0; let mut old_node_present = false; let mut new_node_present = false; let nd = block.node_data.get_mut(); for z in 0 .. 16 { for y in 0 .. 16 { for x in 0 .. 16 { if nd.nodes[idx] == search_id && node_area.contains(Vec3 {x, y, z}) != invert { nd.nodes[idx] = replace_id; new_node_present = true; count += 1; } if nd.nodes[idx] == search_id { old_node_present = true; } idx += 1; } } } // Replacement node not yet in name-ID map; insert it. if new_replace_id && new_node_present { block.nimap.insert(replace_id, new_node); } // Search node was completely eliminated; shift IDs down. if !old_node_present { for i in 0 .. nd.nodes.len() { if nd.nodes[i] > search_id { nd.nodes[i] -= 1; } } block.nimap.remove(search_id); } } // Replace nodes in whole map block. else { // Block already contains replacement node, beware! if let Some(mut replace_id) = block.nimap.get_id(new_node) { let _t = tk.get_timer("replace (non-unique replacement)"); // Delete unused ID from name-ID map and shift IDs down. block.nimap.remove(search_id); // Shift replacement ID, if necessary. replace_id -= (replace_id > search_id) as u16; // Map old node IDs to new node IDs. let nd = block.node_data.get_mut(); for id in &mut nd.nodes { *id = if *id == search_id { count += 1; replace_id } else { *id - (*id > search_id) as u16 }; } } // Block does not contain replacement node. // Simply replace the node name in the name-ID map. else { let _t = tk.get_timer("replace (unique replacement)"); let nd = block.node_data.get_ref(); for id in &nd.nodes { count += (*id == search_id) as u64; } block.nimap.insert(search_id, new_node); } } count } fn replace_nodes(inst: &mut InstBundle) { let node = to_bytes(inst.args.node.as_ref().unwrap()); let new_node = to_bytes(inst.args.new_node.as_ref().unwrap()); let keys = query_keys(&mut inst.db, &inst.status, std::slice::from_ref(&node), inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count = 0; let mut tk = TimeKeeper::new(); for key in keys { let data = inst.db.get_block(key).unwrap(); let mut block = { let _t = tk.get_timer("decode"); MapBlock::deserialize(&data).unwrap() }; if let Some(search_id) = block.nimap.get_id(&node) { count += do_replace(&mut block, key, search_id, &new_node, inst.args.area, inst.args.invert, &mut tk); let new_data = { let _t = tk.get_timer("encode"); block.serialize() }; inst.db.set_block(key, &new_data).unwrap(); } inst.status.inc_done(); } // tk.print(); inst.status.end_editing(); inst.status.log_info(format!("{} nodes replaced.", fmt_big_num(count))); } fn verify_args(args: &InstArgs) -> anyhow::Result<()> { anyhow::ensure!(args.node != args.new_node, "node and new_node must be different."); Ok(()) } pub fn get_command() -> Command { Command { func: replace_nodes, verify_args: Some(verify_args), args: vec![ (ArgType::Node(true), "Name of node to replace"), (ArgType::NewNode, "Name of node to replace with"), (ArgType::Area(false), "Area in which to replace nodes"), (ArgType::Invert, "Replace nodes outside the given area") ], help: "Replace all of one node with another node." } }