use super::{Command, ArgResult}; use crate::unwrap_or; use crate::spatial::{Vec3, Area, InverseBlockIterator}; use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::map_block::MapBlock; use crate::utils::{query_keys, to_bytes, fmt_big_num}; fn do_replace( block: &mut MapBlock, key: i64, old_id: u16, new_node: &[u8], area: Option, invert: bool ) -> u64 { let block_pos = Vec3::from_block_key(key); let mut replaced = 0; // Replace nodes in a portion of a mapblock. if area .filter(|a| a.contains_block(block_pos) != a.touches_block(block_pos)) .is_some() { let node_area = area.unwrap().rel_block_overlap(block_pos).unwrap(); let (new_id, new_id_needed) = match block.nimap.get_id(new_node) { Some(id) => (id, false), None => (block.nimap.get_max_id().unwrap() + 1, true) }; let nd = block.node_data.get_mut(); if invert { for idx in InverseBlockIterator::new(node_area) { if nd.nodes[idx] == old_id { nd.nodes[idx] = new_id; replaced += 1; } } } else { for pos in &node_area { let idx = (pos.x + 16 * (pos.y + 16 * pos.z)) as usize; if nd.nodes[idx] == old_id { nd.nodes[idx] = new_id; replaced += 1; } } } // If replacement ID is not in the name-ID map but was used, add it. if new_id_needed && replaced > 0 { block.nimap.0.insert(new_id, new_node.to_vec()); } // If all instances of the old ID were replaced, remove the old ID. if !nd.nodes.contains(&old_id) { for node in &mut nd.nodes { *node -= (*node > old_id) as u16; } block.nimap.remove_shift(old_id); } } // Replace nodes in whole mapblock. else { // Block already contains replacement node, beware! if let Some(mut new_id) = block.nimap.get_id(new_node) { // Delete unused ID from name-ID map and shift IDs down. block.nimap.remove_shift(old_id); // Shift replacement ID, if necessary. new_id -= (new_id > old_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 == old_id { replaced += 1; new_id } else { *id - (*id > old_id) as u16 }; } } // Block does not contain replacement node. // Simply replace the node name in the name-ID map. else { let nd = block.node_data.get_ref(); for id in &nd.nodes { replaced += (*id == old_id) as u64; } block.nimap.0.insert(old_id, new_node.to_vec()); } } replaced } fn replace_nodes(inst: &mut InstBundle) { let old_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(&old_node), inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count = 0; for key in keys { let data = inst.db.get_block(key).unwrap(); let mut block = unwrap_or!(MapBlock::deserialize(&data), { inst.status.inc_failed(); continue; }); if let Some(old_id) = block.nimap.get_id(&old_node) { count += do_replace(&mut block, key, old_id, &new_node, inst.args.area, inst.args.invert); let new_data = block.serialize(); inst.db.set_block(key, &new_data).unwrap(); } inst.status.inc_done(); } inst.status.end_editing(); inst.status.log_info(format!("{} nodes replaced.", fmt_big_num(count))); } fn verify_args(args: &InstArgs) -> ArgResult { if args.node == args.new_node { return ArgResult::error("node and new_node must be different."); } ArgResult::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." } }