From 661889b852e23c2f0bf7a91033b7ae44598cba7f Mon Sep 17 00:00:00 2001 From: random-geek <35757396+random-geek@users.noreply.github.com> Date: Fri, 26 Mar 2021 00:08:54 -0700 Subject: [PATCH] Completion Part 4 --- Manual.md | 65 ++++++++++++++++++---------------- src/cmd_line.rs | 34 +++++++++--------- src/commands/replace_in_inv.rs | 28 ++++++++------- src/commands/replace_nodes.rs | 4 +-- src/commands/set_meta_var.rs | 53 ++++++++++++++++++--------- src/commands/set_param2.rs | 15 ++++---- src/commands/vacuum.rs | 2 +- src/instance.rs | 8 ++--- src/map_block/metadata.rs | 12 ++++++- 9 files changed, 128 insertions(+), 93 deletions(-) diff --git a/Manual.md b/Manual.md index 51cc394..931a0d0 100644 --- a/Manual.md +++ b/Manual.md @@ -174,27 +174,26 @@ mapblocks can be copied verbatim. Usage: `replaceininv [--delete] [--deletemeta] [--nodes ] [--p1 x y z] [--p2 x y z] [--invert] [new_item]` -Replace or delete certain items in node inventories. +Replace, delete, or modify items in certain node inventories. Arguments: -- `item`: Name of item to replace/delete -- `new_item`: Name of new item, if replacing items. +- ``: Name of the item to replace/delete +- `[new_item]`: Name of the new item, if replacing items. - `--delete`: Delete items instead of replacing them. -- `--deletemeta`: Delete metadata of items. May be used with or without -`new_item`, depending on whether items should also be replaced. -- `--nodes`: Names of one or more nodes to replace in. If not specified, the -item will be replaced in all node inventories. +- `--deletemeta`: Delete metadata of affected items. May be used with or +without `new_item`, depending on whether items should also be replaced. +- `--nodes `: Names of one or more nodes to modify inventories of. If +not specified, items will be modified in any node with an inventory. - `--p1, --p2`: Area in which to modify node inventories. If not specified, -items will be replaced in all node inventories. -- `--invert`: Only modify node inventories *outside* the given area. +items will be modified everywhere. +- `--invert`: Modify node inventories *outside* the given area. Examples: Replace all written books in chests with unwritten books, deleting metadata: -`replaceininv default:book_written default:book --deletemeta --nodes -default:chest default:chest_locked` +`replaceininv default:book_written default:book --deletemeta --nodes default:chest default:chest_locked` ### replacenodes @@ -207,41 +206,45 @@ This command does not affect param2, metadata, etc. Arguments: -- `node`: Name of node to replace. -- `new_node`: Name of node to replace with. -- `--p1, --p2`: Area in which to replace nodes. If not specified, nodes -will be replaced across the entire map. -- `--invert`: Only replace nodes *outside* the given area. +- ``: Name of node to replace. +- ``: Name of node to replace with. +- `--p1, --p2`: Area in which to replace nodes. If not specified, nodes will be +replaced across the entire map. +- `--invert`: 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. +Set or delete a variable in node metadata of certain nodes. 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. +- ``: Name of variable to set/delete, e.g. `infotext`, `formspec`, etc. +- ``: Value to set variable to, if setting a value. This should be a +string. +- `--delete`: Delete the variable. +- `--nodes `: Names of one or more nodes to modify. If not specified, +any node with the given variable will be modified. +- `--p1, --p2`: Area in which to modify node metadata. +- `--invert`: Modify node metadata *outside* the given area. ### setparam2 -Usage: `setparam2 [--node ] [--p1 x y z] [--p2 x y z] [--invert] ` +Usage: `setparam2 [--node ] [--p1 x y z] [--p2 x y z] [--invert] ` -Set param2 values of a certain node and/or within a certain area. +Set param2 values of certain nodes. 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. -- `--invert`: Only set param2 *outside* the given area. +- ``: New param2 value, between 0 and 255. +- `--node `: Name of node to modify. If not specified, the param2 values +of any node will be set. +- `--p1, --p2`: Area in which to set param2 values. +- `--invert`: Set param2 values *outside* the given area. + +An area and/or node is required for setparam2. ### vacuum diff --git a/src/cmd_line.rs b/src/cmd_line.rs index a48c8fc..af1ec76 100644 --- a/src/cmd_line.rs +++ b/src/cmd_line.rs @@ -48,7 +48,7 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) .help(help_msg) ]; } - // TODO: Ensure arguments are correctly defined. + let arg = match arg_type { ArgType::Area(_) => unreachable!(), ArgType::InputMapPath => @@ -65,10 +65,9 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) .value_names(&["x", "y", "z"]) .required(req), ArgType::Node(req) => { - let a = Arg::with_name("node") - .required(req); + let a = Arg::with_name("node"); if req { - a + a.required(true) } else { a.long("node").takes_value(true) } @@ -96,22 +95,21 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) ArgType::NewItem => Arg::with_name("new_item") .takes_value(true), + ArgType::Delete => + Arg::with_name("delete") + .long("delete"), ArgType::DeleteMeta => Arg::with_name("delete_meta") .long("deletemeta"), - ArgType::DeleteItem => - Arg::with_name("delete_item") - .long("delete"), ArgType::Key => Arg::with_name("key") .takes_value(true) .required(true), ArgType::Value => Arg::with_name("value") - .takes_value(true) - .required(true), - ArgType::Param2Val => - Arg::with_name("param2_val") + .takes_value(true), + ArgType::Param2 => + Arg::with_name("param2") .required(true), }.help(help_msg); @@ -129,6 +127,7 @@ fn parse_cmd_line_args() -> anyhow::Result { SubCommand::with_name(cmd_name) .about(cmd.help) .args(&args) + .after_help("For additional information, see the manual.") }); let app = App::new("MapEditr") @@ -144,10 +143,9 @@ fn parse_cmd_line_args() -> anyhow::Result { .global(true) .help("Skip the default confirmation prompt.") ) - // TODO: Move map arg to subcommands? .arg(Arg::with_name("map") .required(true) - .help("Path to world directory or map database to edit.") + .help("Path to world directory or map database to edit") ) .setting(AppSettings::SubcommandRequired) .subcommands(app_commands); @@ -164,9 +162,9 @@ fn parse_cmd_line_args() -> anyhow::Result { input_map_path: sub_matches.value_of("input_map").map(str::to_string), area: { let p1_maybe = sub_matches.values_of("p1").map(arg_to_pos) - .transpose().context("Invalid p1 value")?; + .transpose().context("Invalid p1 value.")?; let p2_maybe = sub_matches.values_of("p2").map(arg_to_pos) - .transpose().context("Invalid p2 value")?; + .transpose().context("Invalid p2 value.")?; if let (Some(p1), Some(p2)) = (p1_maybe, p2_maybe) { Some(Area::from_unsorted(p1, p2)) } else { @@ -175,7 +173,7 @@ fn parse_cmd_line_args() -> anyhow::Result { }, invert: sub_matches.is_present("invert"), offset: sub_matches.values_of("offset").map(arg_to_pos).transpose() - .context("Invalid offset value")?, + .context("Invalid offset value.")?, node: sub_matches.value_of("node").map(str::to_string), nodes: sub_matches.values_of("nodes").iter_mut().flatten() .map(str::to_string).collect(), @@ -185,11 +183,11 @@ fn parse_cmd_line_args() -> anyhow::Result { items: sub_matches.values_of("items") .map(|v| v.map(str::to_string).collect()), new_item: sub_matches.value_of("new_item").map(str::to_string), + delete: sub_matches.is_present("delete"), delete_meta: sub_matches.is_present("delete_meta"), - delete_item: sub_matches.is_present("delete_item"), key: sub_matches.value_of("key").map(str::to_string), value: sub_matches.value_of("value").map(str::to_string), - param2_val: sub_matches.value_of("param2_val").map(|val| val.parse()) + param2: sub_matches.value_of("param2_val").map(|val| val.parse()) .transpose().context("Invalid param2 value.")?, }) } diff --git a/src/commands/replace_in_inv.rs b/src/commands/replace_in_inv.rs index 8f362d1..d74336e 100644 --- a/src/commands/replace_in_inv.rs +++ b/src/commands/replace_in_inv.rs @@ -6,15 +6,15 @@ use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt}; use crate::utils::{query_keys, to_bytes, fmt_big_num}; -const NEWLINE: u8 = b'\n'; -const SPACE: u8 = b' '; - 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 delete = new_item.is_empty(); - let mut new_inv = Vec::with_capacity(inv.len()); + let mut new_inv = Vec::new(); let mut mods = 0; for line in inv.split(|&x| x == NEWLINE) { @@ -66,7 +66,7 @@ fn do_replace(inv: &mut Vec, item: &[u8], new_item: &[u8], del_meta: bool) fn replace_in_inv(inst: &mut InstBundle) { let item = to_bytes(inst.args.item.as_ref().unwrap()); let new_item = inst.args.new_item.as_ref().map(to_bytes) - .unwrap_or(if inst.args.delete_item { vec![] } else { item.clone() }); + .unwrap_or(if inst.args.delete { vec![] } else { item.clone() }); let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect(); let keys = query_keys(&mut inst.db, &mut inst.status, @@ -132,12 +132,14 @@ fn replace_in_inv(inst: &mut InstBundle) { fn verify_args(args: &InstArgs) -> ArgResult { - if args.new_item.is_none() && !args.delete_item && !args.delete_meta { + if args.new_item.is_none() && !args.delete && !args.delete_meta { return ArgResult::error( "new_item is required unless --delete or --deletemeta is used."); - } else if args.new_item.is_some() && args.delete_item { + } else if args.new_item.is_some() && args.delete { return ArgResult::error( "Cannot delete items if new_item is specified."); + } else if args.item == args.new_item && !args.delete_meta { + return ArgResult::error("item and new_item cannot be the same."); } ArgResult::Ok } @@ -150,13 +152,15 @@ pub fn get_command() -> Command { args: vec![ (ArgType::Item, "Name of the item to replace/delete"), (ArgType::NewItem, "Name of the new item, if replacing items."), + (ArgType::Delete, "Delete items instead of replacing them."), (ArgType::DeleteMeta, "Delete metadata of affected items."), - (ArgType::DeleteItem, "Delete items instead of replacing them."), - (ArgType::Area(false), "Area in which to modify inventories"), - (ArgType::Invert, "Modify inventories outside the given area."), - (ArgType::Nodes, "Names of nodes to modify inventories of"), + (ArgType::Nodes, + "Names of one or more nodes to modify inventories of"), + (ArgType::Area(false), "Area in which to modify node inventories"), + (ArgType::Invert, + "Modify node inventories *outside* the given area."), ], - help: "Replace or delete items in node inventories." + help: "Replace, delete, or modify items in certain node inventories." } } diff --git a/src/commands/replace_nodes.rs b/src/commands/replace_nodes.rs index 2630707..e157348 100644 --- a/src/commands/replace_nodes.rs +++ b/src/commands/replace_nodes.rs @@ -19,7 +19,7 @@ fn do_replace( let block_pos = Vec3::from_block_key(key); let mut replaced = 0; - // Replace nodes in a portion of a mapblock. + // Replace nodes in a portion of the mapblock. if area .filter(|a| a.contains_block(block_pos) != a.touches_block(block_pos)) .is_some() @@ -144,7 +144,7 @@ pub fn get_command() -> Command { (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") + (ArgType::Invert, "Replace nodes *outside* the given area.") ], help: "Replace all of one node with another node." } diff --git a/src/commands/set_meta_var.rs b/src/commands/set_meta_var.rs index e435b76..fd1c668 100644 --- a/src/commands/set_meta_var.rs +++ b/src/commands/set_meta_var.rs @@ -1,16 +1,30 @@ -use super::Command; +use super::{Command, ArgResult}; use crate::unwrap_or; use crate::spatial::Vec3; -use crate::instance::{ArgType, InstBundle}; +use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::map_block::{MapBlock, NodeMetadataList, NodeMetadataListExt}; use crate::utils::{query_keys, to_bytes, fmt_big_num}; +fn verify_args(args: &InstArgs) -> ArgResult { + if args.value.is_none() && !args.delete { + return ArgResult::error( + "value is required unless deleting the variable."); + } else if args.value.is_some() && args.delete { + return ArgResult::error( + "value cannot be used when deleting the variable."); + } else if args.value == Some(String::new()) { + return ArgResult::error("Metadata value cannot be empty."); + } + ArgResult::Ok +} + + fn set_meta_var(inst: &mut InstBundle) { - // TODO: Bytes input, create/delete variables + // TODO: Bytes input let key = to_bytes(inst.args.key.as_ref().unwrap()); - let value = to_bytes(inst.args.value.as_ref().unwrap()); + let value = to_bytes(inst.args.value.as_ref().unwrap_or(&String::new())); let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect(); let keys = query_keys(&mut inst.db, &mut inst.status, @@ -41,10 +55,9 @@ fn set_meta_var(inst: &mut InstBundle) { for (&idx, data) in &mut meta { 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 { + if a.contains(pos + block_corner) == inst.args.invert { continue; } } @@ -54,8 +67,13 @@ fn set_meta_var(inst: &mut InstBundle) { continue; } - if let Some(val) = data.vars.get_mut(&key) { - val.0 = value.clone(); + if data.vars.contains_key(&key) { + if inst.args.delete { + // Note: serialize() will cull any newly empty metadata. + data.vars.remove(&key); + } else { + data.vars.get_mut(&key).unwrap().0 = value.clone(); + } modified = true; count += 1; } @@ -76,17 +94,18 @@ fn set_meta_var(inst: &mut InstBundle) { pub fn get_command() -> Command { Command { func: set_meta_var, - verify_args: None, + verify_args: Some(verify_args), args: vec![ - (ArgType::Key, "Name of key to set in metadata"), - (ArgType::Value, "Value to set in metadata"), - (ArgType::Area(false), - "Optional area in which to modify node metadata"), - (ArgType::Invert, "Modify node metadata outside the given area."), + (ArgType::Key, "Name of variable to set/delete"), + (ArgType::Value, "Value to set variable to, if setting a value"), + (ArgType::Delete, "Delete the variable."), (ArgType::Nodes, - "Names of one or more nodes to modify. If not specified, all \ - nodes with the specified variable will be modified.") + "Names of one or more nodes to modify. If not specified, any \ + node with the given variable will be modified."), + (ArgType::Area(false), + "Area in which to modify node metadata"), + (ArgType::Invert, "Modify node metadata *outside* the given area."), ], - help: "Set a variable in node metadata." + help: "Set or delete a variable in node metadata of certain nodes." } } diff --git a/src/commands/set_param2.rs b/src/commands/set_param2.rs index a1eb48c..e9d6a74 100644 --- a/src/commands/set_param2.rs +++ b/src/commands/set_param2.rs @@ -51,7 +51,7 @@ fn set_param2_partial(block: &mut MapBlock, area: Area, invert: bool, fn set_param2(inst: &mut InstBundle) { - let param2_val = inst.args.param2_val.unwrap(); + let param2_val = inst.args.param2.unwrap(); let node = inst.args.node.as_ref().map(to_bytes); let keys = query_keys(&mut inst.db, &mut inst.status, @@ -103,13 +103,14 @@ fn set_param2(inst: &mut InstBundle) { inst.status.end_editing(); tk.print(&mut inst.status); - inst.status.log_info(format!("{} nodes set.", fmt_big_num(count))); + inst.status.log_info(format!("Set param2 of {} nodes.", + fmt_big_num(count))); } fn verify_args(args: &InstArgs) -> ArgResult { if args.area.is_none() && args.node.is_none() { - return ArgResult::error("An area and/or node must be provided."); + return ArgResult::error("An area and/or node is required."); } ArgResult::Ok @@ -121,11 +122,11 @@ pub fn get_command() -> Command { func: set_param2, verify_args: Some(verify_args), args: vec![ - (ArgType::Area(false), "Area in which to set param2 values"), - (ArgType::Invert, "Set param2 values outside the given area."), + (ArgType::Param2, "New param2 value, between 0 and 255"), (ArgType::Node(false), "Node to set param2 values of"), - (ArgType::Param2Val, "New param2 value") + (ArgType::Area(false), "Area in which to set param2 values"), + (ArgType::Invert, "Set param2 values *outside* the given area."), ], - help: "Set param2 value of certain nodes." + help: "Set param2 values of certain nodes." } } diff --git a/src/commands/vacuum.rs b/src/commands/vacuum.rs index 98f1dbe..dd45ffd 100644 --- a/src/commands/vacuum.rs +++ b/src/commands/vacuum.rs @@ -25,6 +25,6 @@ pub fn get_command() -> Command { func: vacuum, verify_args: None, args: Vec::new(), - help: "Rebuild map database to reduce its size." + help: "Rebuild the map database to reduce its size." } } diff --git a/src/instance.rs b/src/instance.rs index 1d628e2..46c8aad 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -23,11 +23,11 @@ pub enum ArgType { Item, Items, NewItem, + Delete, DeleteMeta, - DeleteItem, Key, Value, - Param2Val, + Param2, } @@ -47,11 +47,11 @@ pub struct InstArgs { pub item: Option, pub items: Option>, pub new_item: Option, + pub delete: bool, pub delete_meta: bool, - pub delete_item: bool, pub key: Option, pub value: Option, - pub param2_val: Option, + pub param2: Option, } diff --git a/src/map_block/metadata.rs b/src/map_block/metadata.rs index 82bb527..5f4bd1a 100644 --- a/src/map_block/metadata.rs +++ b/src/map_block/metadata.rs @@ -6,6 +6,9 @@ use std::cmp::min; use memmem::{Searcher, TwoWaySearcher}; +const END_STR: &[u8; 13] = b"EndInventory\n"; + + #[derive(Debug, Clone)] pub struct NodeMetadata { pub vars: HashMap, (Vec, bool)>, @@ -29,7 +32,6 @@ impl NodeMetadata { vars.insert(name.clone(), (val, private)); } - const END_STR: &[u8; 13] = b"EndInventory\n"; let end_finder = TwoWaySearcher::new(END_STR); let end = end_finder .search_in(&data.get_ref()[data.position() as usize ..]) @@ -54,6 +56,11 @@ impl NodeMetadata { data.write_all(&self.inv).unwrap(); } + + /// Return `true` if the metadata contains no variables or inventory lists. + fn is_empty(&self) -> bool { + self.vars.is_empty() && self.inv.starts_with(END_STR) + } } @@ -102,6 +109,9 @@ impl NodeMetadataListExt for NodeMetadataList { data.write_u16::(self.len() as u16).unwrap(); for (&pos, meta) in self { + if meta.is_empty() { + continue; // Skip empty metadata. + } data.write_u16::(pos).unwrap(); meta.serialize(&mut data, version); }