diff --git a/Manual.md b/Manual.md index 65d5413..9d425e0 100644 --- a/Manual.md +++ b/Manual.md @@ -164,6 +164,28 @@ generated in the destination map, or the entire selection if an offset is used. **Tip:** Overlay will be significantly faster if no offset is used, as mapblocks can be copied verbatim. +### replaceininv + +Usage: `replaceininv [--deletemeta] [--node ] [--p1 x y z] [--p2 x y z] [--invert] ` + +Replace a certain item with another in node inventories. To delete items +instead of replacing them, use "Empty" (with a capital E) for `replacename`. + +Arguments: + +- `item`: Name of item to replace +- `new_item`: Name of new item to replace with +- `--deletemeta`: Delete metadata of replaced items. If not specified, any item +metadata will remain unchanged. +- `--node`: Name of node to to replace in. If not specified, the item will be +replaced in all node inventories. +- `--p1, --p2`: Area in which to search for nodes. If not specified, items will +be replaced across the entire map. +- `--invert`: Only search for nodes *outside* the given area. + +**Tip:** To only delete metadata without replacing the nodes, use the +`--deletemeta` flag, and make `new_item` the same as `item`. + ### replacenodes Usage: `replacenodes [--p1 x y z] [--p2 x y z] [--invert] ` @@ -181,6 +203,22 @@ Arguments: will be replaced across the entire map. - `--invert`: Only replace nodes *outside* the given area. +### setmetavar + +Usage: `setmetavar [--node ] [--p1 x y z] [--p2 x y z] [--invert] ` + +Set a variable in node metadata. This only works on metadata where the variable +is already set. + +Arguments: + +- `key`: Name of variable to set, e.g. `infotext`, `formspec`, etc. +- `value`: Value to set variable to. This should be a string. +- `--node`: Name of node to modify. If not specified, the variable will be +set for all nodes that have it. +- `--p1, --p2`: Area in which to modify nodes. +- `--invert`: Only modify nodes *outside* the given area. + ### setparam2 Usage: `setparam2 [--node ] [--p1 x y z] [--p2 x y z] [--invert] ` @@ -192,8 +230,7 @@ Arguments: - `param2_val`: Param2 value to set, between 0 and 255. - `--node`: Name of node to modify. If not specified, the param2 values of all nodes will be set. -- `--p1, --p2`: Area in which to set param2. Required if `--node` is -not specified. +- `--p1, --p2`: Area in which to set param2. - `--invert`: Only set param2 *outside* the given area. ### vacuum @@ -210,42 +247,3 @@ or deleted. **Note:** Because data is copied into another file, vacuum could require as much free disk space as is already occupied by the map. For example, if map.sqlite is 10 GB, make sure you have **at least 10 GB** of free space! - - - - - - -# Danger Zone! - -### `setmetavar` - -**Usage:** `setmetavar [--searchnode ] [--p1 x y z] [--p2 x y z] [--invert] ` - -Set a variable in node metadata. This only works on metadata where the variable is already set. - -Arguments: - -- **`metakey`**: Name of variable to set, e.g. `infotext`, `formspec`, etc. -- **`metavalue`**: Value to set variable to. This should be a string. -- **`--searchnode`**: Name of node to search for. If not specified, the variable will be set for all nodes that have it. -- **`--p1, --p2`**: Area in which to search. Required if `searchnode` is not specified. -- **`--invert`**: Only search for nodes *outside* the given area. - -### `replaceininv` - -**Usage:** ` replaceininv [--deletemeta] [--searchnode ] [--p1 x y z] [--p2 x y z] [--invert] ` - -Replace a certain item with another in node inventories. -To delete items instead of replacing them, use "Empty" (with a capital E) for `replacename`. - -Arguments: - -- **`searchitem`**: Item to search for in node inventories. -- **`replaceitem`**: Item to replace with in node inventories. -- **`--deletemeta`**: Delete metadata of replaced items. If not specified, any item metadata will remain unchanged. -- **`--searchnode`**: Name of node to to replace in. If not specified, the item will be replaced in all node inventories. -- **`--p1, --p2`**: Area in which to search for nodes. If not specified, items will be replaced across the entire map. -- **`--invert`**: Only search for nodes *outside* the given area. - -**Tip:** To only delete metadata without replacing the nodes, use the `--deletemeta` flag, and make `replaceitem` the same as `searchitem`. diff --git a/src/cmd_line.rs b/src/cmd_line.rs index eefe890..a3d4c59 100644 --- a/src/cmd_line.rs +++ b/src/cmd_line.rs @@ -87,6 +87,16 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) a } }, + ArgType::Item => + Arg::with_name("item") + .takes_value(true) + .required(true) + .help(help), + ArgType::NewItem => + Arg::with_name("new_item") + .takes_value(true) + .required(true) + .help(help), ArgType::Param2Val(_) => Arg::with_name("param2_val") .required(true) @@ -102,7 +112,17 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) .long("items") .min_values(0) .max_values(1) - .help(help) + .help(help), + ArgType::Key => + Arg::with_name("key") + .takes_value(true) + .required(true) + .help(help), + ArgType::Value => + Arg::with_name("value") + .takes_value(true) + .required(true) + .help(help), }] } @@ -157,11 +177,15 @@ fn parse_cmd_line_args() -> anyhow::Result { .context("Invalid offset value")?, node: sub_matches.value_of("node").map(str::to_string), new_node: sub_matches.value_of("new_node").map(str::to_string), + item: sub_matches.value_of("item").map(str::to_string), + new_item: sub_matches.value_of("new_item").map(str::to_string), param2_val: sub_matches.value_of("param2_val") .map(|v| v.parse().unwrap()), object: sub_matches.value_of("object").map(str::to_string), items: sub_matches.values_of("items") .map(|v| v.map(str::to_string).collect()), + key: sub_matches.value_of("key").map(str::to_string), + value: sub_matches.value_of("value").map(str::to_string), }) } diff --git a/src/commands/delete_metadata.rs b/src/commands/delete_meta.rs similarity index 100% rename from src/commands/delete_metadata.rs rename to src/commands/delete_meta.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 15f34c1..e8c9ef7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,12 +4,14 @@ use crate::instance::{ArgType, InstArgs, InstBundle}; mod clone; mod delete_blocks; -mod delete_metadata; +mod delete_meta; mod delete_objects; mod delete_timers; mod fill; mod overlay; +mod replace_in_inv; mod replace_nodes; +mod set_meta_var; mod set_param2; mod vacuum; @@ -32,12 +34,14 @@ pub fn get_commands() -> BTreeMap<&'static str, Command> { new_cmd!("clone", clone); new_cmd!("deleteblocks", delete_blocks); - new_cmd!("deletemeta", delete_metadata); + new_cmd!("deletemeta", delete_meta); new_cmd!("deleteobjects", delete_objects); new_cmd!("deletetimers", delete_timers); new_cmd!("fill", fill); new_cmd!("replacenodes", replace_nodes); + new_cmd!("replaceininv", replace_in_inv); new_cmd!("overlay", overlay); + new_cmd!("setmetavar", set_meta_var); new_cmd!("setparam2", set_param2); new_cmd!("vacuum", vacuum); diff --git a/src/commands/replace_in_inv.rs b/src/commands/replace_in_inv.rs new file mode 100644 index 0000000..6f4a204 --- /dev/null +++ b/src/commands/replace_in_inv.rs @@ -0,0 +1,123 @@ +use super::Command; + +use crate::unwrap_or; +use crate::spatial::Vec3; +use crate::instance::{ArgType, InstBundle}; +use crate::map_block::{MapBlock, NodeMetadataList}; +use crate::utils::{query_keys, fmt_big_num}; + + +fn do_replace(inv: &mut Vec, item: &[u8], new_item: &[u8], del_meta: bool) + -> u64 +{ + const NEWLINE: u8 = b'\n'; + const SPACE: u8 = b' '; + + let mut new_inv = Vec::new(); + let mut mods = 0; + for line in inv.split(|&x| x == NEWLINE) { + let parts: Vec<&[u8]> = line.splitn(4, |&x| x == SPACE).collect(); + + if parts[0] == b"Item" && parts[1] == item { + new_inv.extend_from_slice(b"Item "); + new_inv.extend_from_slice(new_item); + if let Some(count) = parts.get(2) { + new_inv.push(SPACE); + new_inv.extend_from_slice(count); + } + if !del_meta { + if let Some(meta) = parts.get(3) { + new_inv.push(SPACE); + new_inv.extend_from_slice(meta); + } + } + mods += 1; + } else { + new_inv.extend_from_slice(line); + } + new_inv.push(NEWLINE); + } + + *inv = new_inv; + mods +} + + +fn replace_in_inv(inst: &mut InstBundle) { + let item = inst.args.item.as_ref().unwrap().as_bytes().to_owned(); + let new_item = inst.args.new_item.as_ref().unwrap().as_bytes().to_owned(); + let del_meta = false; + let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned()); + + let keys = query_keys(&mut inst.db, &mut inst.status, + node.as_deref(), inst.args.area, inst.args.invert, true); + + inst.status.begin_editing(); + let mut item_mods: u64 = 0; + let mut node_mods: u64 = 0; + + for key in keys { + inst.status.inc_done(); + let data = inst.db.get_block(key).unwrap(); + let mut block = unwrap_or!(MapBlock::deserialize(&data), continue); + + let node_data = block.node_data.get_ref(); + let node_id = node.as_deref().and_then(|n| block.nimap.get_id(n)); + if node.is_some() && node_id.is_none() { + continue; // Block doesn't contain the required node. + } + + let mut meta = unwrap_or!( + NodeMetadataList::deserialize(block.metadata.get_ref()), continue); + + let block_corner = Vec3::from_block_key(key) * 16; + let mut modified = false; + + for (&idx, data) in &mut meta.list { + let pos = Vec3::from_u16_key(idx); + let abs_pos = pos + block_corner; + if let Some(a) = inst.args.area { + if a.contains(abs_pos) == inst.args.invert { + continue; + } + } + if let Some(id) = node_id { + if node_data.nodes[idx as usize] != id { + continue; + } + } + + let i_mods = do_replace(&mut data.inv, &item, &new_item, del_meta); + item_mods += i_mods; + if i_mods > 0 { + node_mods += 1; + modified = true; + } + } + + if modified { + *block.metadata.get_mut() = meta.serialize(block.version); + inst.db.set_block(key, &block.serialize()).unwrap(); + } + } + + inst.status.end_editing(); + inst.status.log_info(format!("Replaced {} item stacks in {} nodes.", + fmt_big_num(item_mods), fmt_big_num(node_mods))); +} + + +pub fn get_command() -> Command { + Command { + func: replace_in_inv, + verify_args: None, + args: vec![ + (ArgType::Item, "Name of item to replace"), + (ArgType::NewItem, "Name of new item to replace with"), + (ArgType::Area(false), "Area in which to modify inventories"), + (ArgType::Invert, "Modify inventories outside the given area."), + (ArgType::Node(false), "Node to modify inventories of") + ], + help: "Replace items in node inventories." + } +} diff --git a/src/commands/set_meta_var.rs b/src/commands/set_meta_var.rs new file mode 100644 index 0000000..b0a4852 --- /dev/null +++ b/src/commands/set_meta_var.rs @@ -0,0 +1,87 @@ +use super::Command; + +use crate::unwrap_or; +use crate::spatial::Vec3; +use crate::instance::{ArgType, InstBundle}; +use crate::map_block::{MapBlock, NodeMetadataList}; +use crate::utils::{query_keys, fmt_big_num}; + + +fn set_meta_var(inst: &mut InstBundle) { + let key = inst.args.key.as_ref().unwrap().as_bytes().to_owned(); + let value = inst.args.value.as_ref().unwrap().as_bytes().to_owned(); + let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned()); + + let keys = query_keys(&mut inst.db, &mut inst.status, + node.as_deref(), inst.args.area, inst.args.invert, true); + + inst.status.begin_editing(); + let mut count: u64 = 0; + + for block_key in keys { + inst.status.inc_done(); + let data = inst.db.get_block(block_key).unwrap(); + let mut block = unwrap_or!(MapBlock::deserialize(&data), continue); + + let node_data = block.node_data.get_ref(); + let node_id = node.as_deref().and_then(|n| block.nimap.get_id(n)); + if node.is_some() && node_id.is_none() { + continue; // Block doesn't contain the required node. + } + + let mut meta = unwrap_or!( + NodeMetadataList::deserialize(block.metadata.get_ref()), continue); + + let block_corner = Vec3::from_block_key(block_key) * 16; + let mut modified = false; + + for (&idx, data) in &mut meta.list { + let pos = Vec3::from_u16_key(idx); + let abs_pos = pos + block_corner; + + if let Some(a) = inst.args.area { + if a.contains(abs_pos) == inst.args.invert { + continue; + } + } + if let Some(id) = node_id { + if node_data.nodes[idx as usize] != id { + continue; + } + } + + if let Some(val) = data.vars.get_mut(&key) { + val.0 = value.clone(); + modified = true; + count += 1; + } + } + + if modified { + *block.metadata.get_mut() = meta.serialize(block.version); + inst.db.set_block(block_key, &block.serialize()).unwrap(); + } + } + + inst.status.end_editing(); + inst.status.log_info( + format!("Set metadata variable of {} nodes.", fmt_big_num(count))); +} + + +pub fn get_command() -> Command { + Command { + func: set_meta_var, + verify_args: None, + args: vec![ + (ArgType::Key, "Name of key to set in metadata"), + (ArgType::Value, "Value to set in metadata"), + (ArgType::Area(false), "Area in which to modify node metadata"), + (ArgType::Invert, "Modify node metadata outside the given area."), + (ArgType::Node(false), + "Node to modify metadata in. If not specified, all relevant \ + metadata will be modified.") + ], + help: "Set a variable in node metadata." + } +} diff --git a/src/instance.rs b/src/instance.rs index 2961dfe..d2f1c97 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -17,9 +17,13 @@ pub enum ArgType { Offset(bool), Node(bool), NewNode(bool), + Item, + NewItem, Param2Val(bool), Object(bool), Items, + Key, + Value, } @@ -33,9 +37,13 @@ pub struct InstArgs { pub offset: Option, pub node: Option, pub new_node: Option, + pub item: Option, + pub new_item: Option, pub param2_val: Option, pub object: Option, pub items: Option>, + pub key: Option, + pub value: Option, } @@ -248,12 +256,7 @@ fn compute_thread(args: InstArgs, status: StatusServer) }; let func = commands[args.command.as_str()].func; - let mut inst = InstBundle { - args, - status, - db, - idb - }; + let mut inst = InstBundle {args, status, db, idb}; func(&mut inst); if inst.db.is_in_transaction() { diff --git a/src/main.rs b/src/main.rs index 4087604..f40cdd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,8 @@ mod commands; mod cmd_line; -// Todo: Check for unnecessary #derives! +// TODO: Check for unnecessary #derives! +// TODO: Check mapedit TODOs and implement what's needed. fn main() { // TODO: Add GUI. hmm... cmd_line::run_cmd_line(); diff --git a/src/map_block/map_block.rs b/src/map_block/map_block.rs index f8600c0..455d984 100644 --- a/src/map_block/map_block.rs +++ b/src/map_block/map_block.rs @@ -2,7 +2,6 @@ use super::*; const MIN_BLOCK_VER: u8 = 25; const MAX_BLOCK_VER: u8 = 28; - const BLOCK_BUF_SIZE: usize = 2048; diff --git a/src/spatial/area.rs b/src/spatial/area.rs index 4fca63d..8b13b08 100644 --- a/src/spatial/area.rs +++ b/src/spatial/area.rs @@ -149,21 +149,30 @@ mod tests { Area::from_unsorted(Vec3::new(10, 80, 42), Vec3::new(10, -50, 99)), Area::new(Vec3::new(10, -50, 42), Vec3::new(10, 80, 99)) ); + + assert_eq!( + Area::new(Vec3::new(0, 0, 0), Vec3::new(0, 0, 0)).volume(), 1); + assert_eq!( + Area::new(Vec3::new(0, -9, 14), Vec3::new(19, 0, 17)).volume(), + 800); } #[test] fn test_area_iteration() { - let a = Area::new(Vec3::new(0, -1, -2), Vec3::new(5, 7, 11)); - let mut iter = a.iterate(); - - for z in -2..=11 { - for y in -1..=7 { - for x in 0..=5 { - assert_eq!(iter.next(), Some(Vec3::new(x, y, z))); + fn iter_area(a: Area) { + let mut iter = a.iterate(); + for z in a.min.z..=a.max.z { + for y in a.min.y..=a.max.y { + for x in a.min.x..=a.max.x { + assert_eq!(iter.next(), Some(Vec3::new(x, y, z))) + } } } + assert_eq!(iter.next(), None); } - assert_eq!(iter.next(), None); + iter_area(Area::new(Vec3::new(-1, -1, -1), Vec3::new(-1, -1, -1))); + iter_area(Area::new(Vec3::new(10, -99, 11), Vec3::new(10, -99, 12))); + iter_area(Area::new(Vec3::new(0, -1, -2), Vec3::new(5, 7, 11))); } } diff --git a/src/spatial/mod.rs b/src/spatial/mod.rs index a00a28e..3b7fe2b 100644 --- a/src/spatial/mod.rs +++ b/src/spatial/mod.rs @@ -1,15 +1,13 @@ use std::cmp::{min, max}; mod vec3; -// TODO -// mod v3f; mod area; pub use vec3::Vec3; -// pub use v3f::V3f; pub use area::Area; +// TODO: Should these go in the area impl? pub fn area_contains_block(area: &Area, block_pos: Vec3) -> bool { let corner = block_pos * 16; area.min.x <= corner.x && corner.x + 15 <= area.max.x diff --git a/src/spatial/v3f.rs b/src/spatial/v3f.rs deleted file mode 100644 index 3a72a2c..0000000 --- a/src/spatial/v3f.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct V3f { - pub x: f32, - pub y: f32, - pub z: f32 -} - -impl V3f { - pub fn new(x: f32, y: f32, z: f32) -> Self { - Self {x, y, z} - } -} - -impl std::ops::Add for V3f { - type Output = Self; - - fn add(self, rhs: Self) -> Self { - Self { - x: self.x + rhs.x, - y: self.y + rhs.y, - z: self.z + rhs.z - } - } -} - -impl std::ops::Add for V3f { - type Output = Self; - - fn add(self, rhs: f32) -> Self { - Self { - x: self.x + rhs, - y: self.y + rhs, - z: self.z + rhs - } - } -} - -impl std::ops::Sub for V3f { - type Output = Self; - - fn sub(self, rhs: Self) -> Self { - Self { - x: self.x - rhs.x, - y: self.y - rhs.y, - z: self.z - rhs.z - } - } -} - -impl std::ops::Mul for V3f { - type Output = Self; - - fn mul(self, rhs: Self) -> Self { - Self { - x: self.x * rhs.x, - y: self.y * rhs.y, - z: self.z * rhs.z - } - } -} - -impl std::ops::Mul for V3f { - type Output = Self; - - fn mul(self, rhs: f32) -> Self { - Self { - x: self.x * rhs, - y: self.y * rhs, - z: self.z * rhs - } - } -}