diff --git a/src/cmd_line.rs b/src/cmd_line.rs index e7f6d55..c7b9b71 100644 --- a/src/cmd_line.rs +++ b/src/cmd_line.rs @@ -48,6 +48,7 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) .help(help) ]; } + // TODO: Help is redundant. vec![match arg { ArgType::InputMapPath => Arg::with_name("input_map") @@ -76,6 +77,11 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str)) a } }, + ArgType::Nodes => + Arg::with_name("nodes") + .long("nodes") + .min_values(1) + .help(help), ArgType::NewNode => Arg::with_name("new_node") .takes_value(true) @@ -169,6 +175,8 @@ fn parse_cmd_line_args() -> anyhow::Result { offset: sub_matches.values_of("offset").map(arg_to_pos).transpose() .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(), 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), diff --git a/src/commands/clone.rs b/src/commands/clone.rs index ccf4d06..2e34159 100644 --- a/src/commands/clone.rs +++ b/src/commands/clone.rs @@ -17,7 +17,7 @@ fn clone(inst: &mut InstBundle) { let offset = inst.args.offset.unwrap(); let dst_area = src_area + offset; let mut keys = query_keys(&mut inst.db, &inst.status, - Vec::new(), Some(dst_area), false, true); + &[], Some(dst_area), false, true); // Sort blocks according to offset such that we don't read blocks that // have already been written. diff --git a/src/commands/delete_blocks.rs b/src/commands/delete_blocks.rs index 250c2e1..8a5d678 100644 --- a/src/commands/delete_blocks.rs +++ b/src/commands/delete_blocks.rs @@ -5,8 +5,8 @@ use crate::utils::query_keys; fn delete_blocks(inst: &mut InstBundle) { - let keys = query_keys(&mut inst.db, &inst.status, Vec::new(), - inst.args.area, inst.args.invert, false); + let keys = query_keys(&mut inst.db, &inst.status, + &[], inst.args.area, inst.args.invert, false); inst.status.begin_editing(); for key in keys { diff --git a/src/commands/delete_meta.rs b/src/commands/delete_meta.rs index 815f89c..c15b687 100644 --- a/src/commands/delete_meta.rs +++ b/src/commands/delete_meta.rs @@ -4,14 +4,14 @@ 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}; +use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num}; fn delete_metadata(inst: &mut InstBundle) { - let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned()); + let node = inst.args.node.as_ref().map(to_bytes); let keys = query_keys(&mut inst.db, &mut inst.status, - node.iter().collect(), inst.args.area, inst.args.invert, true); + &to_slice(&node), inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count: u64 = 0; diff --git a/src/commands/delete_objects.rs b/src/commands/delete_objects.rs index f7ed059..ef74c27 100644 --- a/src/commands/delete_objects.rs +++ b/src/commands/delete_objects.rs @@ -4,7 +4,7 @@ use crate::unwrap_or; use crate::spatial::Area; use crate::instance::{ArgType, InstBundle}; use crate::map_block::{MapBlock, StaticObject, LuaEntityData}; -use crate::utils::{query_keys, fmt_big_num}; +use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num}; use memmem::{Searcher, TwoWaySearcher}; @@ -54,19 +54,18 @@ fn delete_objects(inst: &mut InstBundle) { let search_obj = if inst.args.items.is_some() { Some(ITEM_ENT_NAME.to_owned()) } else { - inst.args.object.as_ref().map(|s| s.as_bytes().to_owned()) + inst.args.object.as_ref().map(to_bytes) }; // search_item will be Some if (1) item search is enabled and (2) an item // is specified. - let search_item = inst.args.items.as_ref() - .and_then(|items| items.get(0)) - .map(|s| s.as_bytes().to_owned()); + let search_item = inst.args.items.as_ref().and_then(|items| items.get(0)) + .map(to_bytes); let item_searcher = search_item.as_ref() .map(|s| TwoWaySearcher::new(s)); let keys = query_keys(&mut inst.db, &mut inst.status, - search_obj.iter().collect(), inst.args.area, inst.args.invert, true); + &to_slice(&search_obj), inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count: u64 = 0; diff --git a/src/commands/delete_timers.rs b/src/commands/delete_timers.rs index a9aa5d5..319c1ac 100644 --- a/src/commands/delete_timers.rs +++ b/src/commands/delete_timers.rs @@ -4,14 +4,14 @@ use crate::unwrap_or; use crate::spatial::Vec3; use crate::instance::{ArgType, InstBundle}; use crate::map_block::MapBlock; -use crate::utils::{query_keys, fmt_big_num}; +use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num}; fn delete_timers(inst: &mut InstBundle) { - let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned()); + let node = inst.args.node.as_ref().map(to_bytes); let keys = query_keys(&mut inst.db, &mut inst.status, - node.iter().collect(), inst.args.area, inst.args.invert, true); + &to_slice(&node), inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count: u64 = 0; diff --git a/src/commands/fill.rs b/src/commands/fill.rs index 07d3837..da8f0e0 100644 --- a/src/commands/fill.rs +++ b/src/commands/fill.rs @@ -4,7 +4,7 @@ use crate::spatial::{Vec3, Area, area_rel_block_overlap, area_contains_block}; use crate::instance::{ArgType, InstBundle}; use crate::map_block::{MapBlock}; use crate::block_utils::clean_name_id_map; -use crate::utils::{query_keys, fmt_big_num}; +use crate::utils::{query_keys, to_bytes, fmt_big_num}; fn fill_area(block: &mut MapBlock, area: Area, id: u16) { @@ -23,10 +23,10 @@ fn fill_area(block: &mut MapBlock, area: Area, id: u16) { fn fill(inst: &mut InstBundle) { let area = inst.args.area.unwrap(); - let node = inst.args.new_node.as_ref().unwrap().as_bytes().to_owned(); + let node = to_bytes(inst.args.new_node.as_ref().unwrap()); let keys = query_keys(&mut inst.db, &mut inst.status, - Vec::new(), Some(area), false, true); + &[], Some(area), false, true); inst.status.begin_editing(); diff --git a/src/commands/overlay.rs b/src/commands/overlay.rs index 5b73957..838f787 100644 --- a/src/commands/overlay.rs +++ b/src/commands/overlay.rs @@ -31,7 +31,7 @@ fn overlay_no_offset(inst: &mut InstBundle) { // Get keys from input database. let keys = query_keys(&mut idb, &inst.status, - Vec::new(), inst.args.area, invert, true); + &[], inst.args.area, invert, true); inst.status.begin_editing(); for key in keys { @@ -107,7 +107,7 @@ fn overlay_with_offset(inst: &mut InstBundle) { // Get keys from output database. let keys = query_keys(&mut inst.db, &inst.status, - Vec::new(), dst_area, inst.args.invert, true); + &[], dst_area, inst.args.invert, true); inst.status.begin_editing(); for key in keys { diff --git a/src/commands/replace_in_inv.rs b/src/commands/replace_in_inv.rs index 0f875b8..dd3b2fb 100644 --- a/src/commands/replace_in_inv.rs +++ b/src/commands/replace_in_inv.rs @@ -4,7 +4,7 @@ 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}; +use crate::utils::{query_keys, to_bytes, fmt_big_num}; const NEWLINE: u8 = b'\n'; const SPACE: u8 = b' '; @@ -18,8 +18,12 @@ fn do_replace(inv: &mut Vec, item: &[u8], new_item: &[u8], del_meta: bool) let mut mods = 0; for line in inv.split(|&x| x == NEWLINE) { - let mut parts = line.splitn(4, |&x| x == SPACE); + if line.is_empty() { + // Necessary because of newline after final EndInventory + continue; + } + let mut parts = line.splitn(4, |&x| x == SPACE); if parts.next() == Some(b"Item") && parts.next() == Some(item) { if delete { new_inv.extend_from_slice(b"Empty"); @@ -53,13 +57,13 @@ fn do_replace(inv: &mut Vec, item: &[u8], new_item: &[u8], del_meta: bool) 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 item = to_bytes(inst.args.item.as_ref().unwrap()); + let new_item = to_bytes(inst.args.new_item.as_ref().unwrap()); let del_meta = false; - let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned()); + let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect(); let keys = query_keys(&mut inst.db, &mut inst.status, - node.iter().collect(), inst.args.area, inst.args.invert, true); + &nodes, inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut item_mods: u64 = 0; @@ -71,9 +75,10 @@ fn replace_in_inv(inst: &mut InstBundle) { 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 node_ids: Vec<_> = nodes.iter() + .filter_map(|n| block.nimap.get_id(n)).collect(); + if !nodes.is_empty() && node_ids.is_empty() { + continue; // Block doesn't contain any of the required nodes. } let mut meta = unwrap_or!( @@ -90,10 +95,10 @@ fn replace_in_inv(inst: &mut InstBundle) { continue; } } - if let Some(id) = node_id { - if node_data.nodes[idx as usize] != id { - continue; - } + if !node_ids.is_empty() + && !node_ids.contains(&node_data.nodes[idx as usize]) + { + continue; } let i_mods = do_replace(&mut data.inv, &item, &new_item, del_meta); @@ -125,7 +130,7 @@ pub fn get_command() -> Command { (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") + (ArgType::Nodes, "Names of nodes to modify inventories of") ], help: "Replace items in node inventories." } diff --git a/src/commands/replace_nodes.rs b/src/commands/replace_nodes.rs index db04f4c..d895685 100644 --- a/src/commands/replace_nodes.rs +++ b/src/commands/replace_nodes.rs @@ -4,9 +4,8 @@ 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::utils::query_keys; use crate::time_keeper::TimeKeeper; -use crate::utils::fmt_big_num; +use crate::utils::{query_keys, to_bytes, fmt_big_num}; fn do_replace( @@ -113,10 +112,10 @@ fn do_replace( fn replace_nodes(inst: &mut InstBundle) { - let node = inst.args.node.as_ref().unwrap().as_bytes().to_owned(); - let new_node = inst.args.new_node.as_ref().unwrap().as_bytes().to_owned(); + 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, - vec![&node], inst.args.area, inst.args.invert, true); + std::slice::from_ref(&node), inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count = 0; diff --git a/src/commands/set_meta_var.rs b/src/commands/set_meta_var.rs index 5069113..c2543e8 100644 --- a/src/commands/set_meta_var.rs +++ b/src/commands/set_meta_var.rs @@ -4,16 +4,16 @@ 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}; +use crate::utils::{query_keys, to_bytes, 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 key = to_bytes(inst.args.key.as_ref().unwrap()); + let value = to_bytes(inst.args.value.as_ref().unwrap()); + let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect(); let keys = query_keys(&mut inst.db, &mut inst.status, - node.iter().collect(), inst.args.area, inst.args.invert, true); + &nodes, inst.args.area, inst.args.invert, true); inst.status.begin_editing(); let mut count: u64 = 0; @@ -24,9 +24,10 @@ fn set_meta_var(inst: &mut InstBundle) { 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 node_ids: Vec<_> = nodes.iter() + .filter_map(|n| block.nimap.get_id(n)).collect(); + if !nodes.is_empty() && node_ids.is_empty() { + continue; // Block doesn't contain any of the required nodes. } let mut meta = unwrap_or!( @@ -44,10 +45,10 @@ fn set_meta_var(inst: &mut InstBundle) { continue; } } - if let Some(id) = node_id { - if node_data.nodes[idx as usize] != id { - continue; - } + if !node_ids.is_empty() + && !node_ids.contains(&node_data.nodes[idx as usize]) + { + continue; } if let Some(val) = data.vars.get_mut(&key) { @@ -76,11 +77,12 @@ pub fn get_command() -> Command { 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::Area(false), + "Optional 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.") + (ArgType::Nodes, + "Names of one or more nodes to modify. If not specified, all \ + nodes with the specified variable will be modified.") ], help: "Set a variable in node metadata." } diff --git a/src/commands/set_param2.rs b/src/commands/set_param2.rs index 2164447..79ee8b5 100644 --- a/src/commands/set_param2.rs +++ b/src/commands/set_param2.rs @@ -3,7 +3,7 @@ use super::Command; use crate::spatial::{Vec3, Area, area_rel_block_overlap, area_contains_block}; use crate::instance::{ArgType, InstArgs, InstBundle}; use crate::map_block::{MapBlock}; -use crate::utils::{query_keys, fmt_big_num}; +use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num}; fn set_in_area_node(block: &mut MapBlock, area: Area, id: u16, val: u8) -> u64 @@ -43,10 +43,10 @@ fn set_in_area(block: &mut MapBlock, area: Area, val: u8) { fn set_param2(inst: &mut InstBundle) { let param2_val = inst.args.param2_val.unwrap(); - let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned()); + let node = inst.args.node.as_ref().map(to_bytes); let keys = query_keys(&mut inst.db, &mut inst.status, - node.iter().collect(), inst.args.area, false, true); + to_slice(&node), inst.args.area, false, true); inst.status.begin_editing(); diff --git a/src/instance.rs b/src/instance.rs index 2e6a80e..c62ea40 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -16,6 +16,7 @@ pub enum ArgType { Invert, Offset(bool), Node(bool), + Nodes, NewNode, Item, NewItem, @@ -36,6 +37,7 @@ pub struct InstArgs { pub invert: bool, pub offset: Option, pub node: Option, + pub nodes: Vec, pub new_node: Option, pub item: Option, pub new_item: Option, diff --git a/src/utils.rs b/src/utils.rs index 574ccd9..43c3f64 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,8 +11,7 @@ use crate::spatial::{Area, Vec3}; pub fn query_keys( db: &mut MapDatabase, status: &StatusServer, - // TODO: Allow multiple names for setmetavar and replaceininv. - search_strs: Vec<&Vec>, + search_strs: &[Vec], area: Option, invert: bool, include_partial: bool @@ -21,7 +20,7 @@ pub fn query_keys( // Prepend 16-bit search string length to reduce false positives. // This will break if the name-ID map format changes. - let string16s: Vec> = search_strs.iter().map(|&s| { + let string16s: Vec> = search_strs.iter().map(|s| { let mut res = Vec::new(); res.write_u16::(s.len() as u16).unwrap(); res.extend(s); @@ -49,11 +48,10 @@ pub fn query_keys( continue; } } - if !data_searchers.is_empty() { - // Data must match at least one search string. - if data_searchers.iter().any(|s| s.search_in(&data).is_some()) { - continue; - } + if !data_searchers.is_empty() + && !data_searchers.iter().any(|s| s.search_in(&data).is_some()) + { // Data must match at least one search string. + continue; } keys.push(key); @@ -69,6 +67,19 @@ pub fn query_keys( } +pub fn to_bytes(s: &String) -> Vec { + s.as_bytes().to_vec() +} + + +pub fn to_slice(opt: &Option>) -> &[Vec] { + match opt { + Some(x) => std::slice::from_ref(x), + None => &[] + } +} + + #[macro_export] macro_rules! unwrap_or { ($res:expr, $alt:expr) => {