Completion Part 4

master
random-geek 2021-03-26 00:08:54 -07:00
parent b18b0e96cc
commit 661889b852
9 changed files with 128 additions and 93 deletions

View File

@ -174,27 +174,26 @@ mapblocks can be copied verbatim.
Usage: `replaceininv [--delete] [--deletemeta] [--nodes <nodes>] [--p1 x y z] [--p2 x y z] [--invert] <item> [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.
- `<item>`: 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 <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.
- `<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`: Replace nodes *outside* the given area.
### setmetavar
Usage: `setmetavar [--node <node>] [--p1 x y z] [--p2 x y z] [--invert] <key> <value>`
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.
- `<key>`: Name of variable to set/delete, e.g. `infotext`, `formspec`, etc.
- `<value>`: Value to set variable to, if setting a value. This should be a
string.
- `--delete`: Delete the variable.
- `--nodes <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 <node>] [--p1 x y z] [--p2 x y z] [--invert] <param2_val>`
Usage: `setparam2 [--node <node>] [--p1 x y z] [--p2 x y z] [--invert] <param2>`
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.
- `<param2>`: New param2 value, between 0 and 255.
- `--node <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

View File

@ -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<InstArgs> {
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<InstArgs> {
.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<InstArgs> {
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<InstArgs> {
},
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<InstArgs> {
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.")?,
})
}

View File

@ -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<u8>, 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<u8>, 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."
}
}

View File

@ -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."
}

View File

@ -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."
}
}

View File

@ -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."
}
}

View File

@ -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."
}
}

View File

@ -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<String>,
pub items: Option<Vec<String>>,
pub new_item: Option<String>,
pub delete: bool,
pub delete_meta: bool,
pub delete_item: bool,
pub key: Option<String>,
pub value: Option<String>,
pub param2_val: Option<u8>,
pub param2: Option<u8>,
}

View File

@ -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<u8>, (Vec<u8>, 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::<BigEndian>(self.len() as u16).unwrap();
for (&pos, meta) in self {
if meta.is_empty() {
continue; // Skip empty metadata.
}
data.write_u16::<BigEndian>(pos).unwrap();
meta.serialize(&mut data, version);
}