use std::collections::HashMap;
use std::sync::Arc;
use crate::nbt::{NbtMap, NbtValue};
use super::engine::{Hook, MCValueType, ValueHook};
use super::registry::RegistryBuilder;
use super::types::MapExt;
pub type Renamer = Arc<dyn Fn(&str) -> Option<String> + Send + Sync>;
pub enum RenameSpec {
Pairs(&'static [(&'static str, &'static str)]),
Custom { forward: Renamer, reverse: Renamer },
}
impl RenameSpec {
pub fn custom(forward: Renamer, reverse: Renamer) -> Self {
RenameSpec::Custom { forward, reverse }
}
pub fn forward(&self) -> Renamer {
match self {
RenameSpec::Pairs(pairs) => map_table_renamer(pairs.iter().map(|(o, n)| (*o, *n))),
RenameSpec::Custom { forward, .. } => forward.clone(),
}
}
pub fn reverse(&self) -> Renamer {
match self {
RenameSpec::Pairs(pairs) => map_table_renamer(pairs.iter().map(|(o, n)| (*n, *o))),
RenameSpec::Custom { reverse, .. } => reverse.clone(),
}
}
}
impl From<Renamer> for RenameSpec {
fn from(forward: Renamer) -> Self {
RenameSpec::Custom {
forward,
reverse: Arc::new(|_| None),
}
}
}
fn map_table_renamer(pairs: impl Iterator<Item = (&'static str, &'static str)>) -> Renamer {
let mut map: HashMap<&'static str, &'static str> = HashMap::new();
for (from, to) in pairs {
map.entry(from).or_insert(to);
}
Arc::new(move |id: &str| map.get(id).map(|s| s.to_string()))
}
pub fn map_renamer(pairs: &'static [(&'static str, &'static str)]) -> RenameSpec {
RenameSpec::Pairs(pairs)
}
pub fn invert_pairs(
pairs: &'static [(&'static str, &'static str)],
) -> Vec<(&'static str, &'static str)> {
let mut seen: HashMap<&'static str, &'static str> = HashMap::new();
let mut out = Vec::with_capacity(pairs.len());
for (old, new) in pairs {
debug_assert!(
seen.insert(*new, *old).is_none(),
"non-injective rename table: {new} has multiple preimages"
);
out.push((*new, *old));
}
out
}
fn value_rename_converter(renamer: Renamer) -> super::engine::ValueConverter {
Box::new(move |val: &mut NbtValue, _from, _to| {
if let NbtValue::String(s) = val {
if let Some(new) = renamer(s) {
*s = new;
}
}
})
}
fn name_field_rename_converter(renamer: Renamer) -> super::engine::Converter {
Box::new(move |data: &mut NbtMap, _from, _to| {
if let Some(name) = data.get_string("Name").map(|s| s.to_string()) {
if let Some(new) = renamer(&name) {
data.set_string("Name", new);
}
}
})
}
fn id_field_rename_converter(renamer: Renamer) -> super::engine::Converter {
Box::new(move |data: &mut NbtMap, _from, _to| {
if let Some(id) = data.get_string("id").map(|s| s.to_string()) {
if let Some(new) = renamer(&id) {
data.set_string("id", new);
}
}
})
}
fn flat_state_rename_converter(renamer: Renamer) -> super::engine::ValueConverter {
Box::new(move |val: &mut NbtValue, _from, _to| {
if let NbtValue::String(s) = val {
if s.is_empty() {
return;
}
let end = flat_state_name_end(s);
if let Some(new) = renamer(&s[..end]) {
let rest = s[end..].to_string();
*s = format!("{new}{rest}");
}
}
})
}
pub fn register_value_rename(
ty: &mut MCValueType,
version: i32,
step: i32,
spec: impl Into<RenameSpec>,
) {
let spec = spec.into();
ty.add_converter(version, step, value_rename_converter(spec.forward()));
ty.add_reverse_converter(version, step, value_rename_converter(spec.reverse()));
}
pub fn register_item_rename(reg: &mut RegistryBuilder, version: i32, spec: impl Into<RenameSpec>) {
register_value_rename(&mut reg.item_name, version, 0, spec);
}
pub fn register_entity_rename(
reg: &mut RegistryBuilder,
version: i32,
spec: impl Into<RenameSpec>,
) {
let spec = spec.into();
reg.entity
.add_structure_converter(version, 0, id_field_rename_converter(spec.forward()));
reg.entity
.add_reverse_converter(version, 0, id_field_rename_converter(spec.reverse()));
register_value_rename(&mut reg.entity_name, version, 0, spec);
}
pub fn register_block_rename(reg: &mut RegistryBuilder, version: i32, spec: impl Into<RenameSpec>) {
let spec = spec.into();
register_value_rename(
&mut reg.block_name,
version,
0,
RenameSpec::custom(spec.forward(), spec.reverse()),
);
reg.block_state.add_structure_converter(
version,
0,
name_field_rename_converter(spec.forward()),
);
reg.block_state
.add_reverse_converter(version, 0, name_field_rename_converter(spec.reverse()));
reg.flat_block_state
.add_converter(version, 0, flat_state_rename_converter(spec.forward()));
reg.flat_block_state.add_reverse_converter(
version,
0,
flat_state_rename_converter(spec.reverse()),
);
}
pub fn correct_namespace_or_null(value: &str) -> Option<String> {
if value.is_empty() {
return None;
}
let corrected = correct_namespace(value);
if corrected == value {
None
} else {
Some(corrected)
}
}
pub fn correct_namespace(value: &str) -> String {
match try_parse_identifier(value) {
Some(canonical) => canonical,
None => value.to_string(),
}
}
fn try_parse_identifier(value: &str) -> Option<String> {
let (namespace, path) = match value.find(':') {
Some(i) => (&value[..i], &value[i + 1..]),
None => ("minecraft", value),
};
if !is_valid_namespace(namespace) || !is_valid_path(path) {
return None;
}
Some(format!("{namespace}:{path}"))
}
fn is_valid_namespace(namespace: &str) -> bool {
namespace
.bytes()
.all(|b| matches!(b, b'a'..=b'z' | b'0'..=b'9' | b'.' | b'_' | b'-'))
}
fn is_valid_path(path: &str) -> bool {
path.bytes()
.all(|b| matches!(b, b'a'..=b'z' | b'0'..=b'9' | b'.' | b'_' | b'-' | b'/'))
}
pub fn enforce_namespaced_id_hook(path: &'static str) -> Hook {
Hook {
pre: Some(Box::new(move |data, _from, _to| {
if let Some(id) = data.get_string(path).map(|s| s.to_string()) {
if let Some(new) = correct_namespace_or_null(&id) {
data.set_string(path, new);
}
}
})),
post: None,
}
}
pub fn enforce_namespaced_value_hook() -> ValueHook {
ValueHook {
pre: Some(Box::new(|val, _from, _to| {
if let NbtValue::String(s) = val {
if let Some(new) = correct_namespace_or_null(s) {
*s = new;
}
}
})),
post: None,
}
}
fn flat_state_name_end(s: &str) -> usize {
let b = s.find('[').filter(|&i| i > 0);
let c = s.find('{').filter(|&i| i > 0);
match (b, c) {
(Some(b), Some(c)) => b.min(c),
(Some(b), None) => b,
(None, Some(c)) => c,
(None, None) => s.len(),
}
}
pub fn create_plain_text_component(text: &str) -> String {
serde_json::to_string(&serde_json::json!({ "text": text }))
.expect("serializing a single-key object never fails")
}
pub fn create_translatable_component(key: &str) -> String {
serde_json::to_string(&serde_json::json!({ "translate": key }))
.expect("serializing a single-key object never fails")
}
pub fn is_valid_json(input: &str) -> bool {
serde_json::from_str::<serde_json::Value>(input).is_ok()
}
pub fn convert_from_lenient(input: &str) -> String {
if input.is_empty() || input == "null" {
return create_plain_text_component("");
}
let bytes = input.as_bytes();
let first = bytes[0];
let last = bytes[bytes.len() - 1];
let looks_json = (first == b'"' && last == b'"')
|| (first == b'{' && last == b'}')
|| (first == b'[' && last == b']');
if looks_json {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(input) {
if let serde_json::Value::String(s) = &json {
return create_plain_text_component(s);
}
return serde_json::to_string(&json).expect("re-serialize parsed JSON");
}
}
create_plain_text_component(input)
}