Completion Part 1
This commit is contained in:
parent
0ce828b6fc
commit
a593f141ff
24
Manual.md
24
Manual.md
@ -15,8 +15,8 @@ already be generated. This can be done by either exploring the area in-game,
|
|||||||
or by using Minetest's built-in `/emergeblocks` command.
|
or by using Minetest's built-in `/emergeblocks` command.
|
||||||
|
|
||||||
MapEditr supports all maps created since Minetest version 0.4.2-rc1, released
|
MapEditr supports all maps created since Minetest version 0.4.2-rc1, released
|
||||||
July 2012. Any unsupported areas of the map will be skipped (TODO). Note that
|
July 2012. Any unsupported areas of the map will be skipped. Note that only
|
||||||
only SQLite format maps are currently supported.
|
SQLite format maps are currently supported.
|
||||||
|
|
||||||
## General usage
|
## General usage
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ WorldEdit `//fixlight` command.
|
|||||||
|
|
||||||
Usage: `clone --p1 x y z --p2 x y z --offset x y z`
|
Usage: `clone --p1 x y z --p2 x y z --offset x y z`
|
||||||
|
|
||||||
Clone (copy) a given area to a new location.
|
Clone (copy) the contents of an area to a new location.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
@ -69,14 +69,14 @@ from or into mapblocks that are not yet generated.
|
|||||||
|
|
||||||
Usage: `deleteblocks --p1 x y z --p2 x y z [--invert]`
|
Usage: `deleteblocks --p1 x y z --p2 x y z [--invert]`
|
||||||
|
|
||||||
Deletes all mapblocks in the given area.
|
Delete all mapblocks inside or outside an area.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
- `--p1, --p2`: Area to delete from. Only mapblocks fully inside this area
|
- `--p1, --p2`: Area containing mapblocks to delete. By default, only mapblocks
|
||||||
will be deleted.
|
fully within this area will be deleted.
|
||||||
- `--invert`: Delete only mapblocks that are fully *outside* the given
|
- `--invert`: Delete all mapblocks fully *outside* the given area. Use with
|
||||||
area.
|
caution; you could erase a large portion of your world!
|
||||||
|
|
||||||
**Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen
|
**Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen
|
||||||
will be invoked where the blocks were deleted, and this sometimes causes
|
will be invoked where the blocks were deleted, and this sometimes causes
|
||||||
@ -86,13 +86,13 @@ terrain glitches.
|
|||||||
|
|
||||||
Usage: `deletemeta [--node <node>] [--p1 x y z] [--p2 x y z] [--invert]`
|
Usage: `deletemeta [--node <node>] [--p1 x y z] [--p2 x y z] [--invert]`
|
||||||
|
|
||||||
Delete metadata of a certain node and/or within a certain area. This includes
|
Delete node metadata of certain nodes. Node inventories (such as chest/furnace
|
||||||
node inventories as well.
|
contents) are also deleted.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
- `--node`: Name of node to modify. If not specified, the metadata of all
|
- `--node`: Only delete metadata of nodes with the given name. If not
|
||||||
nodes will be deleted.
|
specified, metadata will be deleted in all matching nodes.
|
||||||
- `--p1, --p2`: Area in which to delete metadata. If not specified, metadata
|
- `--p1, --p2`: Area in which to delete metadata. If not specified, metadata
|
||||||
will be deleted everywhere.
|
will be deleted everywhere.
|
||||||
- `--invert`: Only delete metadata *outside* the given area.
|
- `--invert`: Only delete metadata *outside* the given area.
|
||||||
|
39
README.md
39
README.md
@ -1,36 +1,35 @@
|
|||||||
# MapEditr
|
# MapEditr
|
||||||
|
|
||||||
TODO: Add a license.
|
MapEditr is a command-line tool for fast manipulation of Minetest worlds. It
|
||||||
|
can replace nodes and items, fill areas, combine parts of different worlds, and
|
||||||
MapEditr is a command-line tool for relatively fast manipulation of Minetest
|
much more.
|
||||||
worlds. It can replace nodes, fill areas, combine parts of different worlds,
|
|
||||||
and much more.
|
|
||||||
|
|
||||||
This tool is functionally similar to [WorldEdit][1], but designed for large
|
This tool is functionally similar to [WorldEdit][1], but designed for large
|
||||||
operations that would be impractical to do using WorldEdit. Since it is mainly
|
operations that would be impractical to do within Minetest. Since it is mainly
|
||||||
optimized for speed, MapEditr is not as full-featured as in-game world editors
|
optimized for speed, MapEditr lacks some of the more specialty features of
|
||||||
such as WorldEdit.
|
WorldEdit.
|
||||||
|
|
||||||
MapEditr is originally based on [MapEdit][2], but rewritten in Rust, hence the
|
MapEditr was originally based on [MapEdit][2], except written in Rust rather
|
||||||
added "r". Switching to a compiled language will make MapEditr more robust and
|
than Python (hence the added "r"). Switching to a compiled language will make
|
||||||
easier to maintain in the future.
|
MapEditr more robust and easier to maintain in the future.
|
||||||
|
|
||||||
[1]: https://github.com/Uberi/Minetest-WorldEdit
|
[1]: https://github.com/Uberi/Minetest-WorldEdit
|
||||||
[2]: https://github.com/random-geek/MapEdit
|
[2]: https://github.com/random-geek/MapEdit
|
||||||
|
|
||||||
## Installation
|
## Compilation/Installation
|
||||||
|
|
||||||
TODO: Pre-built binaries
|
TODO: Pre-built binaries
|
||||||
|
|
||||||
To compile from source, you must have Rust installed first, which can be
|
To compile from source, you must have Rust installed first, which can be
|
||||||
downloaded from [here][3]. Then, in the MapEditr directory, run:
|
downloaded from [the Rust website][3]. Then, in the MapEditr directory, simply
|
||||||
|
run:
|
||||||
|
|
||||||
`cargo build --release`
|
`cargo build --release`
|
||||||
|
|
||||||
The `--release` flag is important, as it optimizes the generated executable,
|
The `--release` flag is important, as it produces an optimized executable which
|
||||||
making it much faster.
|
runs much faster than the default, unoptimized version.
|
||||||
|
|
||||||
[3]: https://www.rust-lang.org/tools/install
|
[3]: https://www.rust-lang.org
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -43,10 +42,14 @@ Some useful things you can do with MapEditr:
|
|||||||
- Build extremely long walls and roads in seconds using `fill`.
|
- Build extremely long walls and roads in seconds using `fill`.
|
||||||
- Combine multiple worlds or map saves with `overlay`.
|
- Combine multiple worlds or map saves with `overlay`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
The [Minetest][4] project has been rather important for the making of MapEdit/
|
The [Minetest][4] project has been rather important for the making of
|
||||||
MapEditr, for obvious reasons.
|
MapEdit/MapEditr, for obvious reasons.
|
||||||
|
|
||||||
Some parts of the original MapEdit code were adapted from AndrejIT's
|
Some parts of the original MapEdit code were adapted from AndrejIT's
|
||||||
[map_unexplore][5] project. All due credit goes to the author(s) of that
|
[map_unexplore][5] project. All due credit goes to the author(s) of that
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// TODO: Move this file somewhere else.
|
// TODO: Move this file somewhere else?
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::map_block::{MapBlock, NodeMetadataList};
|
use crate::map_block::{MapBlock, NodeMetadataList};
|
||||||
@ -115,7 +115,7 @@ pub fn clean_name_id_map(block: &mut MapBlock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild the name-ID map.
|
// Rebuild the name-ID map.
|
||||||
let mut new_nimap = BTreeMap::<u16, Vec<u8>>::new();
|
let mut new_nimap = BTreeMap::new();
|
||||||
let mut map = vec![0u16; id_count];
|
let mut map = vec![0u16; id_count];
|
||||||
for id in 0..id_count {
|
for id in 0..id_count {
|
||||||
// Skip unused IDs.
|
// Skip unused IDs.
|
||||||
|
@ -26,9 +26,9 @@ fn arg_to_pos(p: clap::Values) -> anyhow::Result<Vec3> {
|
|||||||
fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
||||||
-> Vec<Arg<'a, 'a>>
|
-> Vec<Arg<'a, 'a>>
|
||||||
{
|
{
|
||||||
let arg = tup.0.clone();
|
let arg_type = tup.0.clone();
|
||||||
let help = tup.1;
|
let help_msg = tup.1;
|
||||||
if let ArgType::Area(req) = arg {
|
if let ArgType::Area(req) = arg_type {
|
||||||
return vec![
|
return vec![
|
||||||
Arg::with_name("p1")
|
Arg::with_name("p1")
|
||||||
.long("p1")
|
.long("p1")
|
||||||
@ -37,7 +37,7 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
|||||||
.value_names(&["x", "y", "z"])
|
.value_names(&["x", "y", "z"])
|
||||||
.required(req)
|
.required(req)
|
||||||
.requires("p2")
|
.requires("p2")
|
||||||
.help(help),
|
.help(help_msg),
|
||||||
Arg::with_name("p2")
|
Arg::with_name("p2")
|
||||||
.long("p2")
|
.long("p2")
|
||||||
.allow_hyphen_values(true)
|
.allow_hyphen_values(true)
|
||||||
@ -45,91 +45,78 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
|||||||
.value_names(&["x", "y", "z"])
|
.value_names(&["x", "y", "z"])
|
||||||
.required(req)
|
.required(req)
|
||||||
.requires("p1")
|
.requires("p1")
|
||||||
.help(help)
|
.help(help_msg)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// TODO: Help is redundant.
|
// TODO: Ensure arguments are correctly defined.
|
||||||
vec![match arg {
|
let arg = match arg_type {
|
||||||
|
ArgType::Area(_) => unreachable!(),
|
||||||
ArgType::InputMapPath =>
|
ArgType::InputMapPath =>
|
||||||
Arg::with_name("input_map")
|
Arg::with_name("input_map")
|
||||||
.required(true)
|
.required(true),
|
||||||
.help(help),
|
|
||||||
ArgType::Area(_) => unreachable!(),
|
|
||||||
ArgType::Invert =>
|
ArgType::Invert =>
|
||||||
Arg::with_name("invert")
|
Arg::with_name("invert")
|
||||||
.long("invert")
|
.long("invert"),
|
||||||
.help(help),
|
|
||||||
ArgType::Offset(req) =>
|
ArgType::Offset(req) =>
|
||||||
Arg::with_name("offset")
|
Arg::with_name("offset")
|
||||||
.long("offset")
|
.long("offset")
|
||||||
.allow_hyphen_values(true)
|
.allow_hyphen_values(true)
|
||||||
.number_of_values(3)
|
.number_of_values(3)
|
||||||
.value_names(&["x", "y", "z"])
|
.value_names(&["x", "y", "z"])
|
||||||
.required(req)
|
.required(req),
|
||||||
.help(help),
|
|
||||||
ArgType::Node(req) => {
|
ArgType::Node(req) => {
|
||||||
let a = Arg::with_name("node")
|
let a = Arg::with_name("node")
|
||||||
.required(req)
|
.required(req);
|
||||||
.help(help);
|
if req {
|
||||||
if !req {
|
|
||||||
a.long("node").takes_value(true)
|
|
||||||
} else {
|
|
||||||
a
|
a
|
||||||
|
} else {
|
||||||
|
a.long("node").takes_value(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ArgType::Nodes =>
|
ArgType::Nodes =>
|
||||||
Arg::with_name("nodes")
|
Arg::with_name("nodes")
|
||||||
.long("nodes")
|
.long("nodes")
|
||||||
.min_values(1)
|
.min_values(1),
|
||||||
.help(help),
|
|
||||||
ArgType::NewNode =>
|
ArgType::NewNode =>
|
||||||
Arg::with_name("new_node")
|
Arg::with_name("new_node")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true),
|
||||||
.help(help),
|
|
||||||
ArgType::Object =>
|
ArgType::Object =>
|
||||||
Arg::with_name("object")
|
Arg::with_name("object")
|
||||||
.long("obj")
|
.long("obj")
|
||||||
.takes_value(true)
|
.takes_value(true),
|
||||||
.help(help),
|
|
||||||
ArgType::Item =>
|
ArgType::Item =>
|
||||||
Arg::with_name("item")
|
Arg::with_name("item")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true),
|
||||||
.help(help),
|
|
||||||
ArgType::Items =>
|
ArgType::Items =>
|
||||||
Arg::with_name("items")
|
Arg::with_name("items")
|
||||||
.long("items")
|
.long("items")
|
||||||
.min_values(0)
|
.min_values(0)
|
||||||
.max_values(1)
|
.max_values(1),
|
||||||
.help(help),
|
|
||||||
ArgType::NewItem =>
|
ArgType::NewItem =>
|
||||||
Arg::with_name("new_item")
|
Arg::with_name("new_item")
|
||||||
.takes_value(true)
|
.takes_value(true),
|
||||||
.help(help),
|
|
||||||
ArgType::DeleteMeta =>
|
ArgType::DeleteMeta =>
|
||||||
Arg::with_name("delete_meta")
|
Arg::with_name("delete_meta")
|
||||||
.long("deletemeta")
|
.long("deletemeta"),
|
||||||
.help(help),
|
|
||||||
ArgType::DeleteItem =>
|
ArgType::DeleteItem =>
|
||||||
Arg::with_name("delete_item")
|
Arg::with_name("delete_item")
|
||||||
.long("delete")
|
.long("delete"),
|
||||||
.help(help),
|
|
||||||
ArgType::Key =>
|
ArgType::Key =>
|
||||||
Arg::with_name("key")
|
Arg::with_name("key")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true),
|
||||||
.help(help),
|
|
||||||
ArgType::Value =>
|
ArgType::Value =>
|
||||||
Arg::with_name("value")
|
Arg::with_name("value")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true)
|
.required(true),
|
||||||
.help(help),
|
|
||||||
ArgType::Param2Val =>
|
ArgType::Param2Val =>
|
||||||
Arg::with_name("param2_val")
|
Arg::with_name("param2_val")
|
||||||
.required(true)
|
.required(true),
|
||||||
.help(help),
|
}.help(help_msg);
|
||||||
}]
|
|
||||||
|
vec![arg]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -147,7 +134,9 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
|
|||||||
|
|
||||||
let app = App::new("MapEditr")
|
let app = App::new("MapEditr")
|
||||||
.about("Edits Minetest worlds/map databases.")
|
.about("Edits Minetest worlds/map databases.")
|
||||||
.after_help("For command-specific help, run: mapeditr <command> -h")
|
.after_help(
|
||||||
|
"For command-specific help, run: mapeditr <SUBCOMMAND> -h\n\
|
||||||
|
For additional information, see the manual.")
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.author(crate_authors!())
|
.author(crate_authors!())
|
||||||
// TODO: Move map arg to subcommands?
|
// TODO: Move map arg to subcommands?
|
||||||
@ -322,7 +311,7 @@ pub fn run_cmd_line() {
|
|||||||
if forced_update == InstState::Querying
|
if forced_update == InstState::Querying
|
||||||
|| (cur_state == InstState::Querying && timed_update_ready)
|
|| (cur_state == InstState::Querying && timed_update_ready)
|
||||||
{
|
{
|
||||||
eprint!("\rQuerying map blocks... {} found.",
|
eprint!("\rQuerying mapblocks... {} found.",
|
||||||
status.get().blocks_total);
|
status.get().blocks_total);
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap();
|
||||||
last_update = now;
|
last_update = now;
|
||||||
|
@ -1,14 +1,29 @@
|
|||||||
use super::{Command, BLOCK_CACHE_SIZE};
|
use super::{Command, BLOCK_CACHE_SIZE};
|
||||||
|
|
||||||
use crate::{unwrap_or, opt_unwrap_or};
|
use crate::{unwrap_or, opt_unwrap_or};
|
||||||
use crate::spatial::Vec3;
|
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||||
use crate::map_database::MapDatabase;
|
use crate::map_database::MapDatabase;
|
||||||
use crate::map_block::{MapBlock, MapBlockError, is_valid_generated,
|
use crate::map_block::{MapBlock, MapBlockError, is_valid_generated,
|
||||||
NodeMetadataList, NodeMetadataListExt};
|
NodeMetadataList, NodeMetadataListExt};
|
||||||
use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
|
use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
|
||||||
use crate::instance::{ArgType, InstBundle};
|
use crate::instance::{ArgType, InstBundle, InstArgs};
|
||||||
use crate::utils::{CacheMap, query_keys};
|
use crate::utils::{CacheMap, query_keys};
|
||||||
use crate::time_keeper::TimeKeeper;
|
|
||||||
|
|
||||||
|
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||||
|
let map_area = Area::new(
|
||||||
|
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
|
||||||
|
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
|
||||||
|
);
|
||||||
|
|
||||||
|
if map_area.intersection(args.area.unwrap() + args.offset.unwrap())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
anyhow::bail!("Destination area is outside map bounds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type BlockResult = Option<Result<MapBlock, MapBlockError>>;
|
type BlockResult = Option<Result<MapBlock, MapBlockError>>;
|
||||||
@ -49,9 +64,8 @@ fn clone(inst: &mut InstBundle) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let mut block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE);
|
let mut block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE);
|
||||||
let mut tk = TimeKeeper::new();
|
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
|
|
||||||
for dst_key in dst_keys {
|
for dst_key in dst_keys {
|
||||||
inst.status.inc_done();
|
inst.status.inc_done();
|
||||||
|
|
||||||
@ -93,40 +107,29 @@ fn clone(inst: &mut InstBundle) {
|
|||||||
let dst_frag_rel = (src_frag_abs + offset)
|
let dst_frag_rel = (src_frag_abs + offset)
|
||||||
.rel_block_overlap(dst_pos).unwrap();
|
.rel_block_overlap(dst_pos).unwrap();
|
||||||
|
|
||||||
{
|
merge_blocks(&src_block, &mut dst_block,
|
||||||
let _t = tk.get_timer("merge");
|
src_frag_rel, dst_frag_rel);
|
||||||
merge_blocks(&src_block, &mut dst_block,
|
merge_metadata(&src_meta, &mut dst_meta,
|
||||||
src_frag_rel, dst_frag_rel);
|
src_frag_rel, dst_frag_rel);
|
||||||
}
|
|
||||||
{
|
|
||||||
let _t = tk.get_timer("merge_meta");
|
|
||||||
merge_metadata(&src_meta, &mut dst_meta,
|
|
||||||
src_frag_rel, dst_frag_rel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let _t = tk.get_timer("name-ID map cleanup");
|
|
||||||
clean_name_id_map(&mut dst_block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clean_name_id_map(&mut dst_block);
|
||||||
*dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version);
|
*dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version);
|
||||||
inst.db.set_block(dst_key, &dst_block.serialize()).unwrap();
|
inst.db.set_block(dst_key, &dst_block.serialize()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.status.end_editing();
|
inst.status.end_editing();
|
||||||
tk.print(&inst.status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_command() -> Command {
|
pub fn get_command() -> Command {
|
||||||
Command {
|
Command {
|
||||||
func: clone,
|
func: clone,
|
||||||
verify_args: None,
|
verify_args: Some(verify_args),
|
||||||
args: vec![
|
args: vec![
|
||||||
(ArgType::Area(true), "Area to clone"),
|
(ArgType::Area(true), "Area to clone"),
|
||||||
(ArgType::Offset(true), "Vector to shift the area by")
|
(ArgType::Offset(true), "Vector to shift the area by")
|
||||||
],
|
],
|
||||||
help: "Clone (copy) a given area to a new location."
|
help: "Clone (copy) the contents of an area to a new location."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ fn delete_blocks(inst: &mut InstBundle) {
|
|||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
|
|
||||||
for key in keys {
|
for key in keys {
|
||||||
// TODO: This is kind of inefficient seeming.
|
|
||||||
inst.status.inc_done();
|
inst.status.inc_done();
|
||||||
inst.db.delete_block(key).unwrap();
|
inst.db.delete_block(key).unwrap();
|
||||||
}
|
}
|
||||||
@ -24,9 +23,10 @@ pub fn get_command() -> Command {
|
|||||||
func: delete_blocks,
|
func: delete_blocks,
|
||||||
verify_args: None,
|
verify_args: None,
|
||||||
args: vec![
|
args: vec![
|
||||||
(ArgType::Area(true), "Area containing blocks to delete"),
|
(ArgType::Area(true), "Area containing mapblocks to delete"),
|
||||||
(ArgType::Invert, "Delete all blocks *outside* the area")
|
(ArgType::Invert,
|
||||||
|
"Delete all mapblocks fully *outside* the given area.")
|
||||||
],
|
],
|
||||||
help: "Delete all map blocks in a given area."
|
help: "Delete all mapblocks inside or outside an area."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ fn delete_metadata(inst: &mut InstBundle) {
|
|||||||
let node = inst.args.node.as_ref().map(to_bytes);
|
let node = inst.args.node.as_ref().map(to_bytes);
|
||||||
|
|
||||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||||
&to_slice(&node), inst.args.area, inst.args.invert, true);
|
to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
let mut count: u64 = 0;
|
let mut count: u64 = 0;
|
||||||
@ -19,7 +19,8 @@ fn delete_metadata(inst: &mut InstBundle) {
|
|||||||
for key in keys {
|
for key in keys {
|
||||||
inst.status.inc_done();
|
inst.status.inc_done();
|
||||||
let data = inst.db.get_block(key).unwrap();
|
let data = inst.db.get_block(key).unwrap();
|
||||||
let mut block = unwrap_or!(MapBlock::deserialize(&data), continue);
|
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||||
|
{ inst.status.inc_failed(); continue; });
|
||||||
|
|
||||||
let node_data = block.node_data.get_ref();
|
let node_data = block.node_data.get_ref();
|
||||||
let node_id = node.as_deref().and_then(|n| block.nimap.get_id(n));
|
let node_id = node.as_deref().and_then(|n| block.nimap.get_id(n));
|
||||||
@ -28,14 +29,14 @@ fn delete_metadata(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut meta = unwrap_or!(
|
let mut meta = unwrap_or!(
|
||||||
NodeMetadataList::deserialize(block.metadata.get_ref()), continue);
|
NodeMetadataList::deserialize(block.metadata.get_ref()),
|
||||||
|
{ inst.status.inc_failed(); continue; });
|
||||||
|
|
||||||
let block_corner = Vec3::from_block_key(key) * 16;
|
let block_corner = Vec3::from_block_key(key) * 16;
|
||||||
let mut to_delete = Vec::with_capacity(meta.len());
|
let mut to_delete = Vec::with_capacity(meta.len());
|
||||||
|
|
||||||
for (&idx, _) in &meta {
|
for (&idx, _) in &meta {
|
||||||
let pos = Vec3::from_u16_key(idx);
|
let abs_pos = Vec3::from_u16_key(idx) + block_corner;
|
||||||
let abs_pos = pos + block_corner;
|
|
||||||
|
|
||||||
if let Some(a) = inst.args.area {
|
if let Some(a) = inst.args.area {
|
||||||
if a.contains(abs_pos) == inst.args.invert {
|
if a.contains(abs_pos) == inst.args.invert {
|
||||||
@ -52,10 +53,10 @@ fn delete_metadata(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !to_delete.is_empty() {
|
if !to_delete.is_empty() {
|
||||||
count += to_delete.len() as u64;
|
|
||||||
for idx in &to_delete {
|
for idx in &to_delete {
|
||||||
meta.remove(idx);
|
meta.remove(idx);
|
||||||
}
|
}
|
||||||
|
count += to_delete.len() as u64;
|
||||||
*block.metadata.get_mut() = meta.serialize(block.version);
|
*block.metadata.get_mut() = meta.serialize(block.version);
|
||||||
inst.db.set_block(key, &block.serialize()).unwrap();
|
inst.db.set_block(key, &block.serialize()).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ fn delete_objects(inst: &mut InstBundle) {
|
|||||||
.map(|s| TwoWaySearcher::new(s));
|
.map(|s| TwoWaySearcher::new(s));
|
||||||
|
|
||||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||||
&to_slice(&search_obj), inst.args.area, inst.args.invert, true);
|
to_slice(&search_obj), inst.args.area, inst.args.invert, true);
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
let mut count: u64 = 0;
|
let mut count: u64 = 0;
|
||||||
|
@ -11,7 +11,7 @@ fn delete_timers(inst: &mut InstBundle) {
|
|||||||
let node = inst.args.node.as_ref().map(to_bytes);
|
let node = inst.args.node.as_ref().map(to_bytes);
|
||||||
|
|
||||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||||
&to_slice(&node), inst.args.area, inst.args.invert, true);
|
to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
let mut count: u64 = 0;
|
let mut count: u64 = 0;
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
use super::Command;
|
use super::Command;
|
||||||
|
|
||||||
use crate::unwrap_or;
|
use crate::unwrap_or;
|
||||||
use crate::spatial::{Vec3, Area};
|
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||||
use crate::instance::{ArgType, InstBundle};
|
use crate::instance::{ArgType, InstBundle};
|
||||||
use crate::map_block::MapBlock;
|
use crate::map_block::MapBlock;
|
||||||
use crate::block_utils::clean_name_id_map;
|
use crate::block_utils::clean_name_id_map;
|
||||||
use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
||||||
|
|
||||||
|
|
||||||
fn fill_area(block: &mut MapBlock, area: Area, id: u16) {
|
fn fill_area(block: &mut MapBlock, id: u16, area: Area, invert: bool) {
|
||||||
let nd = block.node_data.get_mut();
|
let nd = block.node_data.get_mut();
|
||||||
for z in area.min.z ..= area.max.z {
|
|
||||||
let z_start = z * 256;
|
if invert {
|
||||||
for y in area.min.y ..= area.max.y {
|
for i in InverseBlockIterator::new(area) {
|
||||||
let zy_start = z_start + y * 16;
|
nd.nodes[i] = id;
|
||||||
for x in area.min.x ..= area.max.x {
|
}
|
||||||
nd.nodes[(zy_start + x) as usize] = id;
|
} else {
|
||||||
|
for z in area.min.z ..= area.max.z {
|
||||||
|
let z_start = z * 256;
|
||||||
|
for y in area.min.y ..= area.max.y {
|
||||||
|
let zy_start = z_start + y * 16;
|
||||||
|
for x in area.min.x ..= area.max.x {
|
||||||
|
nd.nodes[(zy_start + x) as usize] = id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,7 +34,7 @@ fn fill(inst: &mut InstBundle) {
|
|||||||
let node = to_bytes(inst.args.new_node.as_ref().unwrap());
|
let node = to_bytes(inst.args.new_node.as_ref().unwrap());
|
||||||
|
|
||||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||||
&[], Some(area), false, true);
|
&[], Some(area), inst.args.invert, true);
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
|
|
||||||
@ -42,24 +49,23 @@ fn fill(inst: &mut InstBundle) {
|
|||||||
continue;
|
continue;
|
||||||
});
|
});
|
||||||
|
|
||||||
if area.contains_block(pos) {
|
if area.contains_block(pos) != area.touches_block(pos) {
|
||||||
let nd = block.node_data.get_mut();
|
// Fill part of block
|
||||||
for x in &mut nd.nodes {
|
let block_part = area.rel_block_overlap(pos).unwrap();
|
||||||
*x = 0;
|
|
||||||
}
|
|
||||||
block.nimap.0.clear();
|
|
||||||
block.nimap.0.insert(0, node.to_vec());
|
|
||||||
count += nd.nodes.len() as u64;
|
|
||||||
} else {
|
|
||||||
let slice = area.rel_block_overlap(pos).unwrap();
|
|
||||||
let fill_id = block.nimap.get_id(&node).unwrap_or_else(|| {
|
let fill_id = block.nimap.get_id(&node).unwrap_or_else(|| {
|
||||||
let next = block.nimap.get_max_id().unwrap() + 1;
|
let next = block.nimap.get_max_id().unwrap() + 1;
|
||||||
block.nimap.0.insert(next, node.to_vec());
|
block.nimap.0.insert(next, node.to_vec());
|
||||||
next
|
next
|
||||||
});
|
});
|
||||||
fill_area(&mut block, slice, fill_id);
|
fill_area(&mut block, fill_id, block_part, inst.args.invert);
|
||||||
clean_name_id_map(&mut block);
|
clean_name_id_map(&mut block);
|
||||||
count += slice.volume();
|
count += block_part.volume();
|
||||||
|
} else { // Fill entire block
|
||||||
|
let nd = block.node_data.get_mut();
|
||||||
|
nd.nodes.fill(0);
|
||||||
|
block.nimap.0.clear();
|
||||||
|
block.nimap.0.insert(0, node.to_vec());
|
||||||
|
count += nd.nodes.len() as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.db.set_block(key, &block.serialize()).unwrap();
|
inst.db.set_block(key, &block.serialize()).unwrap();
|
||||||
@ -76,7 +82,8 @@ pub fn get_command() -> Command {
|
|||||||
verify_args: None,
|
verify_args: None,
|
||||||
args: vec![
|
args: vec![
|
||||||
(ArgType::Area(true), "Area to fill"),
|
(ArgType::Area(true), "Area to fill"),
|
||||||
(ArgType::NewNode, "Name of node to fill area with")
|
(ArgType::NewNode, "Name of node to fill area with"),
|
||||||
|
(ArgType::Invert, "Fill all generated areas outside the area.")
|
||||||
],
|
],
|
||||||
help: "Fill the entire area with one node."
|
help: "Fill the entire area with one node."
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{Command, BLOCK_CACHE_SIZE};
|
use super::{Command, BLOCK_CACHE_SIZE};
|
||||||
|
|
||||||
use crate::{unwrap_or, opt_unwrap_or};
|
use crate::{unwrap_or, opt_unwrap_or};
|
||||||
use crate::spatial::{Vec3, Area};
|
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||||
use crate::instance::{ArgType, InstArgs, InstBundle};
|
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||||
use crate::map_database::MapDatabase;
|
use crate::map_database::MapDatabase;
|
||||||
use crate::map_block::{MapBlock, MapBlockError, is_valid_generated,
|
use crate::map_block::{MapBlock, MapBlockError, is_valid_generated,
|
||||||
@ -11,11 +11,24 @@ use crate::utils::{query_keys, CacheMap};
|
|||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||||
let offset_if_nonzero =
|
if args.invert
|
||||||
args.offset.filter(|&off| off != Vec3::new(0, 0, 0));
|
&& args.offset.filter(|&ofs| ofs != Vec3::new(0, 0, 0)).is_some()
|
||||||
if args.invert && offset_if_nonzero.is_some() {
|
{
|
||||||
anyhow::bail!("Inverted selections cannot be offset.");
|
anyhow::bail!("Inverted selections cannot be offset.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let offset = args.offset.unwrap_or(Vec3::new(0, 0, 0));
|
||||||
|
let map_area = Area::new(
|
||||||
|
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
|
||||||
|
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
|
||||||
|
);
|
||||||
|
|
||||||
|
if map_area.intersection(args.area.unwrap_or(map_area) + offset)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
anyhow::bail!("Destination area is outside map bounds.");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,12 +58,12 @@ fn overlay_no_offset(inst: &mut InstBundle) {
|
|||||||
|
|
||||||
if (!invert && area.contains_block(pos))
|
if (!invert && area.contains_block(pos))
|
||||||
|| (invert && !area.touches_block(pos))
|
|| (invert && !area.touches_block(pos))
|
||||||
{ // If possible, copy whole map block.
|
{ // If possible, copy whole mapblock.
|
||||||
let data = idb.get_block(key).unwrap();
|
let data = idb.get_block(key).unwrap();
|
||||||
if is_valid_generated(&data) {
|
if is_valid_generated(&data) {
|
||||||
db.set_block(key, &data).unwrap();
|
db.set_block(key, &data).unwrap();
|
||||||
}
|
}
|
||||||
} else { // Copy part of map block
|
} else { // Copy part of mapblock
|
||||||
let res = || -> Result<(), MapBlockError> {
|
let res = || -> Result<(), MapBlockError> {
|
||||||
let dst_data = opt_unwrap_or!(
|
let dst_data = opt_unwrap_or!(
|
||||||
db.get_block(key).ok()
|
db.get_block(key).ok()
|
||||||
@ -91,7 +104,7 @@ fn overlay_no_offset(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No area; copy whole map block.
|
// No area; copy whole mapblock.
|
||||||
let data = idb.get_block(key).unwrap();
|
let data = idb.get_block(key).unwrap();
|
||||||
if is_valid_generated(&data) {
|
if is_valid_generated(&data) {
|
||||||
db.set_block(key, &data).unwrap();
|
db.set_block(key, &data).unwrap();
|
||||||
|
@ -1,146 +1,127 @@
|
|||||||
use super::Command;
|
use super::Command;
|
||||||
|
|
||||||
use crate::spatial::{Vec3, Area};
|
use crate::unwrap_or;
|
||||||
|
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||||
use crate::instance::{ArgType, InstArgs, InstBundle};
|
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||||
use crate::map_block::MapBlock;
|
use crate::map_block::MapBlock;
|
||||||
use crate::time_keeper::TimeKeeper;
|
|
||||||
use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
||||||
|
|
||||||
|
|
||||||
fn do_replace(
|
fn do_replace(
|
||||||
block: &mut MapBlock,
|
block: &mut MapBlock,
|
||||||
key: i64,
|
key: i64,
|
||||||
search_id: u16,
|
old_id: u16,
|
||||||
new_node: &[u8],
|
new_node: &[u8],
|
||||||
area: Option<Area>,
|
area: Option<Area>,
|
||||||
invert: bool,
|
invert: bool
|
||||||
tk: &mut TimeKeeper
|
|
||||||
) -> u64
|
) -> u64
|
||||||
{
|
{
|
||||||
let block_pos = Vec3::from_block_key(key);
|
let block_pos = Vec3::from_block_key(key);
|
||||||
let mut count = 0;
|
let mut replaced = 0;
|
||||||
|
|
||||||
// Replace nodes in a portion of a map block.
|
// Replace nodes in a portion of a mapblock.
|
||||||
if area.is_some() && area.unwrap().contains_block(block_pos) !=
|
if area
|
||||||
area.unwrap().touches_block(block_pos)
|
.filter(|a| a.contains_block(block_pos) != a.touches_block(block_pos))
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
let _t = tk.get_timer("replace (partial block)");
|
|
||||||
let node_area = area.unwrap().rel_block_overlap(block_pos).unwrap();
|
let node_area = area.unwrap().rel_block_overlap(block_pos).unwrap();
|
||||||
|
|
||||||
let mut new_replace_id = false;
|
let (new_id, new_id_needed) = match block.nimap.get_id(new_node) {
|
||||||
let replace_id = block.nimap.get_id(new_node)
|
Some(id) => (id, false),
|
||||||
.unwrap_or_else(|| {
|
None => (block.nimap.get_max_id().unwrap() + 1, true)
|
||||||
new_replace_id = true;
|
};
|
||||||
block.nimap.get_max_id().unwrap() + 1
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut idx = 0;
|
|
||||||
let mut old_node_present = false;
|
|
||||||
let mut new_node_present = false;
|
|
||||||
|
|
||||||
let nd = block.node_data.get_mut();
|
let nd = block.node_data.get_mut();
|
||||||
for z in 0..16 {
|
|
||||||
for y in 0..16 {
|
|
||||||
for x in 0..16 {
|
|
||||||
if nd.nodes[idx] == search_id
|
|
||||||
&& node_area.contains(Vec3 {x, y, z}) != invert
|
|
||||||
{
|
|
||||||
nd.nodes[idx] = replace_id;
|
|
||||||
new_node_present = true;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if nd.nodes[idx] == search_id {
|
if invert {
|
||||||
old_node_present = true;
|
for idx in InverseBlockIterator::new(node_area) {
|
||||||
}
|
if nd.nodes[idx] == old_id {
|
||||||
idx += 1;
|
nd.nodes[idx] = new_id;
|
||||||
|
replaced += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for pos in &node_area {
|
||||||
|
let idx = (pos.x + 16 * (pos.y + 16 * pos.z)) as usize;
|
||||||
|
if nd.nodes[idx] == old_id {
|
||||||
|
nd.nodes[idx] = new_id;
|
||||||
|
replaced += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replacement node not yet in name-ID map; insert it.
|
// If replacement ID is not in the name-ID map but was used, add it.
|
||||||
if new_replace_id && new_node_present {
|
if new_id_needed && replaced > 0 {
|
||||||
block.nimap.0.insert(replace_id, new_node.to_vec());
|
block.nimap.0.insert(new_id, new_node.to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search node was completely eliminated; shift IDs down.
|
// If all instances of the old ID were replaced, remove the old ID.
|
||||||
if !old_node_present {
|
if !nd.nodes.contains(&old_id) {
|
||||||
for i in 0 .. nd.nodes.len() {
|
for node in &mut nd.nodes {
|
||||||
if nd.nodes[i] > search_id {
|
*node -= (*node > old_id) as u16;
|
||||||
nd.nodes[i] -= 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
block.nimap.remove_shift(search_id);
|
block.nimap.remove_shift(old_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Replace nodes in whole map block.
|
// Replace nodes in whole mapblock.
|
||||||
else {
|
else {
|
||||||
// Block already contains replacement node, beware!
|
// Block already contains replacement node, beware!
|
||||||
if let Some(mut replace_id) = block.nimap.get_id(new_node) {
|
if let Some(mut new_id) = block.nimap.get_id(new_node) {
|
||||||
let _t = tk.get_timer("replace (non-unique replacement)");
|
|
||||||
// Delete unused ID from name-ID map and shift IDs down.
|
// Delete unused ID from name-ID map and shift IDs down.
|
||||||
block.nimap.remove_shift(search_id);
|
block.nimap.remove_shift(old_id);
|
||||||
// Shift replacement ID, if necessary.
|
// Shift replacement ID, if necessary.
|
||||||
replace_id -= (replace_id > search_id) as u16;
|
new_id -= (new_id > old_id) as u16;
|
||||||
|
|
||||||
// Map old node IDs to new node IDs.
|
// Map old node IDs to new node IDs.
|
||||||
let nd = block.node_data.get_mut();
|
let nd = block.node_data.get_mut();
|
||||||
for id in &mut nd.nodes {
|
for id in &mut nd.nodes {
|
||||||
*id = if *id == search_id {
|
*id = if *id == old_id {
|
||||||
count += 1;
|
replaced += 1;
|
||||||
replace_id
|
new_id
|
||||||
} else {
|
} else {
|
||||||
*id - (*id > search_id) as u16
|
*id - (*id > old_id) as u16
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Block does not contain replacement node.
|
// Block does not contain replacement node.
|
||||||
// Simply replace the node name in the name-ID map.
|
// Simply replace the node name in the name-ID map.
|
||||||
else {
|
else {
|
||||||
let _t = tk.get_timer("replace (unique replacement)");
|
|
||||||
let nd = block.node_data.get_ref();
|
let nd = block.node_data.get_ref();
|
||||||
for id in &nd.nodes {
|
for id in &nd.nodes {
|
||||||
count += (*id == search_id) as u64;
|
replaced += (*id == old_id) as u64;
|
||||||
}
|
}
|
||||||
block.nimap.0.insert(search_id, new_node.to_vec());
|
block.nimap.0.insert(old_id, new_node.to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count
|
replaced
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn replace_nodes(inst: &mut InstBundle) {
|
fn replace_nodes(inst: &mut InstBundle) {
|
||||||
let node = to_bytes(inst.args.node.as_ref().unwrap());
|
let old_node = to_bytes(inst.args.node.as_ref().unwrap());
|
||||||
let new_node = to_bytes(inst.args.new_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,
|
let keys = query_keys(&mut inst.db, &inst.status,
|
||||||
std::slice::from_ref(&node), inst.args.area, inst.args.invert, true);
|
std::slice::from_ref(&old_node),
|
||||||
|
inst.args.area, inst.args.invert, true);
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
let mut tk = TimeKeeper::new();
|
|
||||||
for key in keys {
|
for key in keys {
|
||||||
let data = inst.db.get_block(key).unwrap();
|
let data = inst.db.get_block(key).unwrap();
|
||||||
|
|
||||||
let mut block = {
|
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||||
let _t = tk.get_timer("decode");
|
{ inst.status.inc_failed(); continue; });
|
||||||
MapBlock::deserialize(&data).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(search_id) = block.nimap.get_id(&node) {
|
if let Some(old_id) = block.nimap.get_id(&old_node) {
|
||||||
count += do_replace(&mut block, key, search_id, &new_node,
|
count += do_replace(&mut block, key, old_id, &new_node,
|
||||||
inst.args.area, inst.args.invert, &mut tk);
|
inst.args.area, inst.args.invert);
|
||||||
let new_data = {
|
let new_data = block.serialize();
|
||||||
let _t = tk.get_timer("encode");
|
|
||||||
block.serialize()
|
|
||||||
};
|
|
||||||
inst.db.set_block(key, &new_data).unwrap();
|
inst.db.set_block(key, &new_data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.status.inc_done();
|
inst.status.inc_done();
|
||||||
}
|
}
|
||||||
|
|
||||||
// tk.print();
|
|
||||||
inst.status.end_editing();
|
inst.status.end_editing();
|
||||||
inst.status.log_info(format!("{} nodes replaced.", fmt_big_num(count)));
|
inst.status.log_info(format!("{} nodes replaced.", fmt_big_num(count)));
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
|||||||
|
|
||||||
|
|
||||||
fn set_meta_var(inst: &mut InstBundle) {
|
fn set_meta_var(inst: &mut InstBundle) {
|
||||||
|
// TODO: Bytes input
|
||||||
let key = to_bytes(inst.args.key.as_ref().unwrap());
|
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());
|
||||||
let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect();
|
let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect();
|
||||||
|
@ -1,81 +1,89 @@
|
|||||||
use super::Command;
|
use super::Command;
|
||||||
|
|
||||||
use crate::spatial::{Vec3, Area};
|
use crate::unwrap_or;
|
||||||
|
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||||
use crate::instance::{ArgType, InstArgs, InstBundle};
|
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||||
use crate::map_block::MapBlock;
|
use crate::map_block::MapBlock;
|
||||||
use crate::utils::{query_keys, to_bytes, to_slice, 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
|
fn set_param2_partial(block: &mut MapBlock, area: Area, invert: bool,
|
||||||
|
node_id: Option<u16>, val: u8) -> u64
|
||||||
{
|
{
|
||||||
let nd = block.node_data.get_mut();
|
let nd = block.node_data.get_mut();
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
for z in area.min.z ..= area.max.z {
|
|
||||||
let z_start = z * 256;
|
if invert {
|
||||||
for y in area.min.y ..= area.max.y {
|
if let Some(id) = node_id {
|
||||||
let zy_start = z_start + y * 16;
|
for idx in InverseBlockIterator::new(area) {
|
||||||
for x in area.min.x ..= area.max.x {
|
if nd.nodes[idx] == id {
|
||||||
let i = (zy_start + x) as usize;
|
nd.param2[idx] = val;
|
||||||
if nd.nodes[i] == id {
|
|
||||||
nd.param2[i] = val;
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for idx in InverseBlockIterator::new(area) {
|
||||||
|
nd.param2[idx] = val;
|
||||||
|
}
|
||||||
|
count += 4096 - area.volume();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let no_node = node_id.is_none();
|
||||||
|
let id = node_id.unwrap_or(0);
|
||||||
|
|
||||||
|
for z in area.min.z ..= area.max.z {
|
||||||
|
let z_start = z * 256;
|
||||||
|
for y in area.min.y ..= area.max.y {
|
||||||
|
let zy_start = z_start + y * 16;
|
||||||
|
for x in area.min.x ..= area.max.x {
|
||||||
|
let i = (zy_start + x) as usize;
|
||||||
|
if no_node || nd.nodes[i] == id {
|
||||||
|
nd.param2[i] = val;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn set_in_area(block: &mut MapBlock, area: Area, val: u8) {
|
|
||||||
let nd = block.node_data.get_mut();
|
|
||||||
for z in area.min.z ..= area.max.z {
|
|
||||||
let z_start = z * 256;
|
|
||||||
for y in area.min.y ..= area.max.y {
|
|
||||||
let zy_start = z_start + y * 16;
|
|
||||||
for x in area.min.x ..= area.max.x {
|
|
||||||
nd.param2[(zy_start + x) as usize] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn set_param2(inst: &mut InstBundle) {
|
fn set_param2(inst: &mut InstBundle) {
|
||||||
let param2_val = inst.args.param2_val.unwrap();
|
let param2_val = inst.args.param2_val.unwrap();
|
||||||
let node = inst.args.node.as_ref().map(to_bytes);
|
let node = inst.args.node.as_ref().map(to_bytes);
|
||||||
|
|
||||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||||
to_slice(&node), inst.args.area, false, true);
|
to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||||
|
|
||||||
inst.status.begin_editing();
|
inst.status.begin_editing();
|
||||||
|
|
||||||
let mut count: u64 = 0;
|
let mut count: u64 = 0;
|
||||||
|
use crate::time_keeper::TimeKeeper;
|
||||||
|
let mut tk = TimeKeeper::new();
|
||||||
for key in keys {
|
for key in keys {
|
||||||
inst.status.inc_done();
|
inst.status.inc_done();
|
||||||
|
|
||||||
let pos = Vec3::from_block_key(key);
|
let pos = Vec3::from_block_key(key);
|
||||||
let data = inst.db.get_block(key).unwrap();
|
let data = inst.db.get_block(key).unwrap();
|
||||||
let mut block = MapBlock::deserialize(&data).unwrap();
|
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||||
|
{ inst.status.inc_failed(); continue; });
|
||||||
|
|
||||||
let node_id = node.as_ref().and_then(|n| block.nimap.get_id(n));
|
let node_id = node.as_ref().and_then(|n| block.nimap.get_id(n));
|
||||||
if inst.args.node.is_some() && node_id.is_none() {
|
if inst.args.node.is_some() && node_id.is_none() {
|
||||||
// Node not found in this map block.
|
// Node not found in this mapblock.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nd = block.node_data.get_mut();
|
let nd = block.node_data.get_mut();
|
||||||
if let Some(area) = inst.args.area
|
if let Some(area) = inst.args.area
|
||||||
.filter(|a| !a.contains_block(pos))
|
.filter(|a| a.contains_block(pos) != a.touches_block(pos))
|
||||||
{ // Modify part of block
|
{ // Modify part of block
|
||||||
let overlap = area.rel_block_overlap(pos).unwrap();
|
let block_part = area.rel_block_overlap(pos).unwrap();
|
||||||
if let Some(nid) = node_id {
|
let _t = tk.get_timer("set_param2_partial");
|
||||||
count +=
|
count += set_param2_partial(&mut block,
|
||||||
set_in_area_node(&mut block, overlap, nid, param2_val);
|
block_part, inst.args.invert, node_id, param2_val);
|
||||||
} else {
|
|
||||||
set_in_area(&mut block, overlap, param2_val);
|
|
||||||
count += overlap.volume();
|
|
||||||
}
|
|
||||||
} else { // Modify whole block
|
} else { // Modify whole block
|
||||||
if let Some(nid) = node_id {
|
if let Some(nid) = node_id {
|
||||||
for i in 0 .. nd.param2.len() {
|
for i in 0 .. nd.param2.len() {
|
||||||
@ -85,9 +93,7 @@ fn set_param2(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for x in &mut nd.param2 {
|
nd.param2.fill(param2_val);
|
||||||
*x = param2_val;
|
|
||||||
}
|
|
||||||
count += nd.param2.len() as u64;
|
count += nd.param2.len() as u64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,6 +102,7 @@ fn set_param2(inst: &mut InstBundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inst.status.end_editing();
|
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!("{} nodes set.", fmt_big_num(count)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,9 +120,10 @@ pub fn get_command() -> Command {
|
|||||||
verify_args: Some(verify_args),
|
verify_args: Some(verify_args),
|
||||||
args: vec![
|
args: vec![
|
||||||
(ArgType::Area(false), "Area in which to set param2 values"),
|
(ArgType::Area(false), "Area in which to set param2 values"),
|
||||||
|
(ArgType::Invert, "Set param2 values outside the given area."),
|
||||||
(ArgType::Node(false), "Node to set param2 values of"),
|
(ArgType::Node(false), "Node to set param2 values of"),
|
||||||
(ArgType::Param2Val, "New param2 value")
|
(ArgType::Param2Val, "New param2 value")
|
||||||
],
|
],
|
||||||
help: "Set param2 values of an area or node."
|
help: "Set param2 value of certain nodes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::mpsc;
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
use crate::spatial::{Vec3, Area};
|
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||||
use crate::map_database::MapDatabase;
|
use crate::map_database::MapDatabase;
|
||||||
use crate::commands;
|
use crate::commands;
|
||||||
|
|
||||||
@ -193,6 +193,27 @@ fn status_channel() -> (StatusServer, StatusClient) {
|
|||||||
|
|
||||||
|
|
||||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||||
|
// TODO: Complete verifications.
|
||||||
|
|
||||||
|
if args.area.is_none() && args.invert {
|
||||||
|
anyhow::bail!("Cannot invert without a specified area.");
|
||||||
|
}
|
||||||
|
if let Some(a) = args.area {
|
||||||
|
for pos in &[a.min, a.max] {
|
||||||
|
anyhow::ensure!(pos.is_valid_node_pos(),
|
||||||
|
"Area corner is outside map bounds: {}.", pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(offset) = args.offset {
|
||||||
|
let huge = |n| n < -MAP_LIMIT * 2 || n > MAP_LIMIT * 2;
|
||||||
|
|
||||||
|
if huge(offset.x) || huge(offset.y) || huge(offset.z) {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Offset cannot be larger than {} nodes in any direction.",
|
||||||
|
MAP_LIMIT * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_valid_name(name: &str) -> bool {
|
fn is_valid_name(name: &str) -> bool {
|
||||||
if name == "air" || name == "ignore" {
|
if name == "air" || name == "ignore" {
|
||||||
true
|
true
|
||||||
@ -205,29 +226,12 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
|||||||
let mod_name = &name[..delim];
|
let mod_name = &name[..delim];
|
||||||
let item_name = &name[delim + 1..];
|
let item_name = &name[delim + 1..];
|
||||||
|
|
||||||
if mod_name.find(|c: char|
|
mod_name.find(|c: char|
|
||||||
!(c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'))
|
!(c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'))
|
||||||
.is_some()
|
.is_none()
|
||||||
|| item_name.find(|c: char|
|
&& item_name.find(|c: char|
|
||||||
!(c.is_ascii_alphanumeric() || c == '_'))
|
!(c.is_ascii_alphanumeric() || c == '_'))
|
||||||
.is_some()
|
.is_none()
|
||||||
{
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Complete verifications.
|
|
||||||
|
|
||||||
if args.area.is_none() && args.invert {
|
|
||||||
anyhow::bail!("Cannot invert without a specified area.");
|
|
||||||
}
|
|
||||||
if let Some(a) = args.area {
|
|
||||||
for pos in &[a.min, a.max] {
|
|
||||||
anyhow::ensure!(pos.is_valid_node_pos(),
|
|
||||||
"Area corner is outside map bounds: {}.", pos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +250,13 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
|||||||
verify_name!(args.new_node, "Invalid node name: {}");
|
verify_name!(args.new_node, "Invalid node name: {}");
|
||||||
verify_name!(args.object, "Invalid object name: {}");
|
verify_name!(args.object, "Invalid object name: {}");
|
||||||
verify_name!(args.item, "Invalid item name: {}");
|
verify_name!(args.item, "Invalid item name: {}");
|
||||||
|
if let Some(items) = &args.items {
|
||||||
|
for i in items {
|
||||||
|
anyhow::ensure!(is_valid_name(i), "Invalid item name: {}", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify_name!(args.new_item, "Invalid item name: {}");
|
||||||
|
// TODO: Are keys/values escaped?
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,22 @@ impl Area {
|
|||||||
(self.max.z - self.min.z + 1) as u64
|
(self.max.z - self.min.z + 1) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn intersection(&self, rhs: Self) -> Option<Self> {
|
||||||
|
let res = Self {
|
||||||
|
min: Vec3 {
|
||||||
|
x: max(self.min.x, rhs.min.x),
|
||||||
|
y: max(self.min.y, rhs.min.y),
|
||||||
|
z: max(self.min.z, rhs.min.z)
|
||||||
|
},
|
||||||
|
max: Vec3 {
|
||||||
|
x: min(self.max.x, rhs.max.x),
|
||||||
|
y: min(self.max.y, rhs.max.y),
|
||||||
|
z: min(self.max.z, rhs.max.z)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(res).filter(Self::is_valid)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, pos: Vec3) -> bool {
|
pub fn contains(&self, pos: Vec3) -> bool {
|
||||||
self.min.x <= pos.x && pos.x <= self.max.x
|
self.min.x <= pos.x && pos.x <= self.max.x
|
||||||
&& self.min.y <= pos.y && pos.y <= self.max.y
|
&& self.min.y <= pos.y && pos.y <= self.max.y
|
||||||
@ -253,6 +269,36 @@ mod tests {
|
|||||||
iter_area(Area::new(Vec3::new(0, -1, -2), Vec3::new(5, 7, 11)));
|
iter_area(Area::new(Vec3::new(0, -1, -2), Vec3::new(5, 7, 11)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_area_intersection() {
|
||||||
|
let triples = [
|
||||||
|
(
|
||||||
|
Area::new(Vec3::new(0, 0, 0), Vec3::new(0, 0, 0)),
|
||||||
|
Area::new(Vec3::new(1, 1, 0), Vec3::new(1, 1, 0)),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Area::new(Vec3::new(-10, -8, -10), Vec3::new(10, 8, 10)),
|
||||||
|
Area::new(Vec3::new(-12, 0, -2), Vec3::new(-8, 13, 2)),
|
||||||
|
Some(Area::new(Vec3::new(-10, 0, -2), Vec3::new(-8, 8, 2)))
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Area::new(Vec3::new(0, 0, 0), Vec3::new(2, 2, 2)),
|
||||||
|
Area::new(Vec3::new(0, -1, 3), Vec3::new(2, 1, 5)),
|
||||||
|
None
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Area::new(Vec3::new(0, -10, -10), Vec3::new(30, 30, 30)),
|
||||||
|
Area::new(Vec3::new(16, 16, -10), Vec3::new(29, 29, 20)),
|
||||||
|
Some(Area::new(Vec3::new(16, 16, -10), Vec3::new(29, 29, 20)))
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for t in &triples {
|
||||||
|
assert_eq!(t.0.intersection(t.1), t.2);
|
||||||
|
assert_eq!(t.1.intersection(t.0), t.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_area_containment() {
|
fn test_area_containment() {
|
||||||
let area = Area::new(Vec3::new(-1, -32, 16), Vec3::new(30, -17, 54));
|
let area = Area::new(Vec3::new(-1, -32, 16), Vec3::new(30, -17, 54));
|
||||||
|
@ -1,5 +1,112 @@
|
|||||||
mod vec3;
|
mod vec3;
|
||||||
mod area;
|
mod area;
|
||||||
|
|
||||||
pub use vec3::Vec3;
|
pub use vec3::{MAP_LIMIT, Vec3};
|
||||||
pub use area::Area;
|
pub use area::Area;
|
||||||
|
|
||||||
|
|
||||||
|
/// Iterates over all the block indices that are *not* contained within an
|
||||||
|
/// area, in order.
|
||||||
|
pub struct InverseBlockIterator {
|
||||||
|
area: Area,
|
||||||
|
idx: usize,
|
||||||
|
can_skip: bool,
|
||||||
|
skip_pos: Vec3,
|
||||||
|
skip_idx: usize,
|
||||||
|
skip_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InverseBlockIterator {
|
||||||
|
pub fn new(area: Area) -> Self {
|
||||||
|
assert!(area.min.x >= 0 && area.max.x < 16
|
||||||
|
&& area.min.y >= 0 && area.max.y < 16
|
||||||
|
&& area.min.z >= 0 && area.max.z < 16);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
area,
|
||||||
|
idx: 0,
|
||||||
|
can_skip: true,
|
||||||
|
skip_pos: area.min,
|
||||||
|
skip_idx:
|
||||||
|
(area.min.x + area.min.y * 16 + area.min.z * 256) as usize,
|
||||||
|
skip_len: (area.max.x - area.min.x + 1) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for InverseBlockIterator {
|
||||||
|
type Item = usize;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while self.can_skip && self.idx >= self.skip_idx {
|
||||||
|
self.idx += self.skip_len;
|
||||||
|
// Increment self.skip_pos, self.skip_idx.
|
||||||
|
let mut sp = self.skip_pos;
|
||||||
|
sp.y += 1;
|
||||||
|
if sp.y > self.area.max.y {
|
||||||
|
sp.y = self.area.min.y;
|
||||||
|
sp.z += 1;
|
||||||
|
if sp.z > self.area.max.z {
|
||||||
|
// No more skips
|
||||||
|
self.can_skip = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.skip_pos = sp;
|
||||||
|
self.skip_idx = (sp.x + sp.y * 16 + sp.z * 256) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.idx < 4096 {
|
||||||
|
let idx = self.idx;
|
||||||
|
self.idx += 1;
|
||||||
|
Some(idx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inverse_block_iterator() {
|
||||||
|
let dim_pairs = [
|
||||||
|
(1, 14), // Touching neither end
|
||||||
|
(1, 2),
|
||||||
|
(9, 9),
|
||||||
|
(1, 15), // Touching max end
|
||||||
|
(11, 15),
|
||||||
|
(15, 15),
|
||||||
|
(0, 0), // Touching min end
|
||||||
|
(0, 1),
|
||||||
|
(0, 14),
|
||||||
|
(0, 15), // End-to-end
|
||||||
|
];
|
||||||
|
|
||||||
|
fn test_area(area: Area) {
|
||||||
|
let mut iter = InverseBlockIterator::new(area);
|
||||||
|
for pos in &Area::new(Vec3::new(0, 0, 0), Vec3::new(15, 15, 15)) {
|
||||||
|
if !area.contains(pos) {
|
||||||
|
let idx = (pos.x + pos.y * 16 + pos.z * 256) as usize;
|
||||||
|
assert_eq!(iter.next(), Some(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(iter.next(), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
for z_dims in &dim_pairs {
|
||||||
|
for y_dims in &dim_pairs {
|
||||||
|
for x_dims in &dim_pairs {
|
||||||
|
let area = Area::new(
|
||||||
|
Vec3::new(x_dims.0, y_dims.0, z_dims.0),
|
||||||
|
Vec3::new(x_dims.1, y_dims.1, z_dims.1)
|
||||||
|
);
|
||||||
|
test_area(area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
pub const MAP_LIMIT: i32 = 31000;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Vec3 {
|
pub struct Vec3 {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
@ -39,7 +42,7 @@ impl Vec3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_block_pos(&self) -> bool {
|
pub fn is_valid_block_pos(&self) -> bool {
|
||||||
const LIMIT: i32 = 31000 / 16;
|
const LIMIT: i32 = MAP_LIMIT / 16;
|
||||||
|
|
||||||
-LIMIT <= self.x && self.x <= LIMIT
|
-LIMIT <= self.x && self.x <= LIMIT
|
||||||
&& -LIMIT <= self.y && self.y <= LIMIT
|
&& -LIMIT <= self.y && self.y <= LIMIT
|
||||||
@ -47,7 +50,7 @@ impl Vec3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_node_pos(&self) -> bool {
|
pub fn is_valid_node_pos(&self) -> bool {
|
||||||
const LIMIT: i32 = 31000;
|
const LIMIT: i32 = MAP_LIMIT;
|
||||||
|
|
||||||
-LIMIT <= self.x && self.x <= LIMIT
|
-LIMIT <= self.x && self.x <= LIMIT
|
||||||
&& -LIMIT <= self.y && self.y <= LIMIT
|
&& -LIMIT <= self.y && self.y <= LIMIT
|
||||||
|
Loading…
x
Reference in New Issue
Block a user