Fiddling, breaking stuff

master
random-geek 2021-01-20 23:47:35 -08:00
parent b236c6d7d8
commit 2fd016bc22
8 changed files with 103 additions and 91 deletions

View File

@ -74,6 +74,22 @@ area.
will be invoked where the blocks were deleted, and this sometimes causes
terrain glitches.
### deleteobjects
Usage: `deleteobjects [--obj <obj>] [--items] [--p1 x y z] [--p2 x y z] [--invert]`
Delete objects (entities) of a certain name and/or within a certain area.
Arguments:
- `--obj`: Name of object to search for, e.g. "boats:boat". If not specified,
all objects will be deleted.
- `--items`: Search for only item entities (dropped items). If this flag is
set, `--obj` can optionally be used to specify an item name.
- `--p1, --p2`: Area in which to delete objects. If not specified, objects will
be deleted across the entire map.
- `--invert`: Delete objects *outside* the given area.
### fill
Usage: `fill --p1 x y z --p2 x y z [--invert] <new_node>`
@ -154,6 +170,21 @@ all nodes will be set.
not specified.
- `--invert`: Only set param2 *outside* the given area.
### vacuum
Usage: `vacuum`
Vacuums the database. This reduces the size of the database, but may take a
long time.
All this does is perform an SQLite `VACUUM` command. This shrinks and optimizes
the database by efficiently "repacking" all mapblocks. No map data is changed
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!
@ -216,28 +247,3 @@ Arguments:
- **`--searchnode`**: Name of node to search for. If not specified, the node timers of all nodes will be deleted.
- **`--p1, --p2`**: Area in which to delete node timers. Required if `searchnode` is not specified.
- **`--invert`**: Only delete node timers *outside* the given area.
### `deleteobjects`
**Usage:** `deleteobjects [--searchobj <searchobj>] [--items] [--p1 x y z] [--p2 x y z] [--invert]`
Delete static objects of a certain name and/or within a certain area.
Arguments:
- **`--searchobj`**: Name of object to search for, e.g. "boats:boat". If not specified, all objects will be deleted.
- **`--items`**: Search for only item entities (dropped items). `searchobj` determines the item name, if specified.
- **`--p1, --p2`**: Area in which to delete objects. If not specified, objects will be deleted across the entire map.
- **`--invert`**: Only delete objects *outside* the given area.
### `vacuum`
**Usage:** `vacuum`
Vacuums the database. This reduces the size of the database, but may take a long time.
All this does is perform an SQLite `VACUUM` command. This shrinks and optimizes the database by efficiently "repacking" all mapblocks.
No map data is changed or deleted.
**Note:** Because data is copied into another file, this command could require as much free disk space as is already occupied by the map.
For example, if your database is 10 GB, make sure you have **at least 10 GB** of free space!

View File

@ -111,7 +111,7 @@ pub fn clean_name_id_map(block: &mut MapBlock) {
}
// Rebuild the name-ID map.
let mut new_nimap = BTreeMap::<u16, String>::new();
let mut new_nimap = BTreeMap::<u16, Vec<u8>>::new();
let mut map = vec![0u16; id_count];
for id in 0 .. id_count {
// Skip unused IDs.

View File

@ -1,7 +1,8 @@
use super::Command;
use crate::spatial::Area;
use crate::instance::{ArgType, InstBundle};
use crate::map_block::{MapBlock, LuaEntityData};
use crate::map_block::{MapBlock, StaticObject, LuaEntityData};
use crate::utils::{query_keys, fmt_big_num};
use memmem::{Searcher, TwoWaySearcher};
@ -17,21 +18,45 @@ macro_rules! unwrap_or {
}
#[inline]
fn can_delete(
obj: &StaticObject,
area: &Option<Area>,
invert: bool
) -> bool {
// Check area requirements
if let Some(a) = area {
const DIV_FAC: i32 = 10_000;
let rounded_pos = obj.f_pos.map(
|v| (v - DIV_FAC / 2).div_euclid(DIV_FAC));
if a.contains(rounded_pos) == invert {
return false;
}
}
true
}
fn delete_objects(inst: &mut InstBundle) {
const ITEM_ENT_NAME: &'static [u8] = b"__builtin:item";
const ITEM_ENT_NAME: &[u8] = b"__builtin:item";
let search_obj = if inst.args.items {
Some(String::from_utf8(ITEM_ENT_NAME.to_vec()).unwrap())
Some(ITEM_ENT_NAME.to_owned())
} else {
inst.args.object.clone()
inst.args.object.as_ref().map(|s| s.as_bytes().to_owned())
};
let keys = query_keys(&mut inst.db, &mut inst.status,
search_obj.clone(), inst.args.area, inst.args.invert, true);
search_obj.as_deref(), inst.args.area, inst.args.invert, true);
let search_item = search_obj.as_ref().filter(|_| inst.args.items).map(|s|
format!(
"[\"itemstring\"] = \"{}\"",
String::from_utf8(s.to_owned()).unwrap()
).into_bytes()
);
let item_searcher = search_item.as_ref().map(|s| TwoWaySearcher::new(s));
inst.status.begin_editing();
let item_searcher = search_obj.as_ref().filter(|_| inst.args.items)
.map(|s| TwoWaySearcher::new(format!("[itemstring]=\"{}\"", s)));
let mut count: u64 = 0;
for key in keys {
inst.status.inc_done();
@ -42,39 +67,15 @@ fn delete_objects(inst: &mut InstBundle) {
for i in (0..block.static_objects.list.len()).rev() {
let obj = &block.static_objects.list[i];
// Check area requirements
if let Some(area) = inst.args.area {
const DIV_FAC: i32 = 10_000;
let rounded_pos = obj.f_pos.map(
|v| (v - DIV_FAC / 2).div_euclid(DIV_FAC));
if area.contains(rounded_pos) == inst.args.invert {
continue;
}
if can_delete(
&block.static_objects.list[i],
&inst.args.area,
inst.args.invert
) {
block.static_objects.list.remove(i);
modified = true;
count += 1;
}
// Check name requirements
let le_data = unwrap_or!(LuaEntityData::deserialize(&obj),
continue);
if inst.args.items {
if le_data.name != ITEM_ENT_NAME {
continue;
}
if let Some(searcher) = &item_searcher {
if searcher.search_in(&le_data.data).is_none() {
continue;
}
}
} else {
if let Some(sobj) = &search_obj {
if le_data.name != sobj.as_bytes() {
continue;
}
}
}
block.static_objects.list.remove(i);
modified = true;
count += 1;
}
if modified {

View File

@ -23,7 +23,7 @@ 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.clone().unwrap();
let node = inst.args.new_node.as_ref().unwrap().as_bytes().to_owned();
let keys = query_keys(&mut inst.db, &mut inst.status,
None, Some(area), false, true);

View File

@ -13,7 +13,7 @@ fn do_replace(
block: &mut MapBlock,
key: i64,
search_id: u16,
new_node: &str,
new_node: &[u8],
area: Option<Area>,
invert: bool,
tk: &mut TimeKeeper
@ -113,10 +113,10 @@ fn do_replace(
fn replace_nodes(inst: &mut InstBundle) {
let node = inst.args.node.clone().unwrap();
let new_node = inst.args.new_node.clone().unwrap();
let node = inst.args.node.as_ref().unwrap().as_bytes();
let new_node = inst.args.new_node.as_ref().unwrap().as_bytes();
let keys = query_keys(&mut inst.db, &inst.status,
Some(node.clone()), inst.args.area, inst.args.invert, true);
Some(node), inst.args.area, inst.args.invert, true);
inst.status.begin_editing();
let mut count = 0;

View File

@ -1,7 +1,7 @@
use super::Command;
use crate::spatial::{Vec3, Area, area_rel_block_overlap, area_contains_block};
use crate::instance::{ArgType, InstBundle};
use crate::instance::{ArgType, InstArgs, InstBundle};
use crate::map_block::{MapBlock};
use crate::utils::{query_keys, fmt_big_num};
@ -42,12 +42,11 @@ fn set_in_area(block: &mut MapBlock, area: Area, val: u8) {
fn set_param2(inst: &mut InstBundle) {
// TODO: Actually verify!
assert!(inst.args.area.is_some() || inst.args.node.is_some());
let param2_val = inst.args.param2_val.unwrap();
let node = inst.args.node.as_ref().map(|s| s.as_bytes().to_owned());
let keys = query_keys(&mut inst.db, &mut inst.status,
inst.args.node.clone(), inst.args.area, false, true);
node.as_deref(), inst.args.area, false, true);
inst.status.begin_editing();
@ -59,8 +58,7 @@ fn set_param2(inst: &mut InstBundle) {
let data = inst.db.get_block(key).unwrap();
let mut block = MapBlock::deserialize(&data).unwrap();
let node_id = inst.args.node.as_deref()
.and_then(|node| block.nimap.get_id(&node));
let node_id = node.as_ref().and_then(|n| block.nimap.get_id(n));
if inst.args.node.is_some() && node_id.is_none() {
// Node not found in this map block.
continue;
@ -103,10 +101,17 @@ fn set_param2(inst: &mut InstBundle) {
}
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
anyhow::ensure!(args.area.is_some() || args.node.is_some(),
"An area and/or node must be provided.");
Ok(())
}
pub fn get_command() -> Command {
Command {
func: set_param2,
verify_args: None,
verify_args: Some(verify_args),
args: vec![
(ArgType::Area(false), "Area in which to set param2 values"),
(ArgType::Node(false), "Node to set param2 values of"),

View File

@ -8,7 +8,7 @@ use super::*;
#[derive(Debug)]
pub struct NameIdMap {
// Use a BTreeMap instead of a HashMap to preserve the order of IDs.
pub map: BTreeMap<u16, String>,
pub map: BTreeMap<u16, Vec<u8>>,
}
impl NameIdMap {
@ -26,8 +26,7 @@ impl NameIdMap {
for _ in 0 .. count {
let id = data.read_u16::<BigEndian>()?;
let name = read_string16(data)?;
let string = String::from_utf8_lossy(&name).into_owned();
map.insert(id, string);
map.insert(id, name);
}
Ok(Self {map})
@ -39,14 +38,15 @@ impl NameIdMap {
for (id, name) in &self.map {
out.write_u16::<BigEndian>(*id).unwrap();
write_string16(out, name.as_bytes());
write_string16(out, name);
}
}
#[inline]
pub fn get_id(&self, name: &str) -> Option<u16> {
self.map.iter()
.find_map(|(&k, v)| if v == name { Some(k) } else { None })
pub fn get_id(&self, name: &[u8]) -> Option<u16> {
self.map.iter().find_map(|(&k, v)|
if v.as_slice() == name { Some(k) } else { None }
)
}
#[inline]
@ -55,8 +55,8 @@ impl NameIdMap {
}
#[inline]
pub fn insert(&mut self, id: u16, name: &str) {
self.map.insert(id, name.to_string());
pub fn insert(&mut self, id: u16, name: &[u8]) {
self.map.insert(id, name.to_owned());
}
/// Remove the name at a given ID and shift down values above it.

View File

@ -11,7 +11,7 @@ use crate::spatial::{Area, Vec3};
pub fn query_keys(
db: &mut MapDatabase,
status: &StatusServer,
search_str: Option<String>,
search_str: Option<&[u8]>,
area: Option<Area>,
invert: bool,
include_partial: bool
@ -22,8 +22,8 @@ pub fn query_keys(
// This will break if the name-ID map format changes.
let search_bytes = search_str.map(|s| {
let mut res = Vec::new();
res.write_u16::<BigEndian>(s.as_bytes().len() as u16).unwrap();
res.extend(s.as_bytes());
res.write_u16::<BigEndian>(s.len() as u16).unwrap();
res.extend(s);
res
});
let data_searcher = search_bytes.as_ref().map(|b| {