replaceininv

master
random-geek 2021-01-23 18:13:13 -08:00
parent dc2f3d7c05
commit edd54dfa7f
12 changed files with 309 additions and 135 deletions

View File

@ -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 <node>] [--p1 x y z] [--p2 x y z] [--invert] <item> <new_item>`
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] <node> <new_node>`
@ -181,6 +203,22 @@ Arguments:
will be replaced across the entire map.
- `--invert`: Only 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.
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 <node>] [--p1 x y z] [--p2 x y z] [--invert] <param2_val>`
@ -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 <searchnode>] [--p1 x y z] [--p2 x y z] [--invert] <metakey> <metavalue>`
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 <searchnode>] [--p1 x y z] [--p2 x y z] [--invert] <searchitem> <replaceitem>`
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`.

View File

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

View File

@ -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);

View File

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

View File

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

View File

@ -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<Vec3>,
pub node: Option<String>,
pub new_node: Option<String>,
pub item: Option<String>,
pub new_item: Option<String>,
pub param2_val: Option<u8>,
pub object: Option<String>,
pub items: Option<Vec<String>>,
pub key: Option<String>,
pub value: Option<String>,
}
@ -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() {

View File

@ -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();

View File

@ -2,7 +2,6 @@ use super::*;
const MIN_BLOCK_VER: u8 = 25;
const MAX_BLOCK_VER: u8 = 28;
const BLOCK_BUF_SIZE: usize = 2048;

View File

@ -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)));
}
}

View File

@ -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

View File

@ -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<Self> 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<f32> 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<Self> 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<Self> 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<f32> for V3f {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs
}
}
}