1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! V4054 (1.21.1 + 99) — schematic-relevant subset of `V4054.java`.
//!
//! The ominous-banner identity is upgraded: a banner whose `minecraft:item_name`
//! component is the translatable component `block.minecraft.ominous_banner`
//! gains `minecraft:rarity = "uncommon"` and has its item name normalised to the
//! canonical translatable component.
//!
//! * TILE_ENTITY `minecraft:banner` converter.
//! * ITEM_STACK `minecraft:white_banner` converter.
//!
//! Both share `convertComponents`. Component values are stored as JSON strings;
//! `retrieveTranslationString` parses the JSON and pulls the `translate` key,
//! and `createTranslatableComponent` re-emits the canonical stable form
//! (`{"translate":"block.minecraft.ominous_banner"}`).
//!
//! Nothing non-schematic is present in this version.
//!
//! VERSION = MCVersions.V1_21_1 (3955) + 99 = 4054.
use crate::nbt::NbtMap;
use super::super::loss::{report_loss, LossKind, Severity};
use super::super::registry::RegistryBuilder;
use super::super::types::MapExt;
const VERSION: i32 = 4054;
/// `ComponentUtils.retrieveTranslationString`: parse `possible_json` and, if it
/// is an object with a string `translate` field, return that string. Any parse
/// failure or shape mismatch yields `None` (Java returns null).
fn retrieve_translation_string(possible_json: &str) -> Option<String> {
let element: serde_json::Value = serde_json::from_str(possible_json).ok()?;
element.get("translate")?.as_str().map(|s| s.to_string())
}
/// `ComponentUtils.createTranslatableComponent`: the stable JSON form
/// `{"translate":"<key>"}` (single key, so no key ordering concerns).
fn create_translatable_component(key: &str) -> String {
serde_json::json!({ "translate": key }).to_string()
}
/// `convertComponents`: promote an ominous banner's rarity + item name.
fn convert_components(components: Option<&mut NbtMap>) {
let components = match components {
Some(c) => c,
None => return,
};
let item_name_key = components
.get_string("minecraft:item_name")
.and_then(retrieve_translation_string);
if item_name_key.as_deref() != Some("block.minecraft.ominous_banner") {
return;
}
components.set_string("minecraft:rarity", "uncommon");
components.set_string(
"minecraft:item_name",
create_translatable_component("block.minecraft.ominous_banner"),
);
}
/// Reverse of `convertComponents` (V4054.java:30-43).
///
/// The forward step does two additive/normalizing things to an ominous banner
/// (item whose `minecraft:item_name` translates to `block.minecraft.ominous_banner`):
/// * adds `minecraft:rarity = "uncommon"` (bucket D — drop on the way back);
/// * canonicalizes `minecraft:item_name` to `{"translate":"…"}` (a pure JSON
/// normalization whose inverse is identity — the pre-canonical text cannot
/// be recovered, but the logical value is unchanged, so this is not a loss).
///
/// We identify the ominous banner via the (canonical) item name, then drop the
/// rarity the forward inserted, but only when it still equals the value the
/// forward set ("uncommon") so we never discard a user-authored rarity.
fn reverse_convert_components(components: Option<&mut NbtMap>) {
let components = match components {
Some(c) => c,
None => return,
};
let item_name_key = components
.get_string("minecraft:item_name")
.and_then(retrieve_translation_string);
if item_name_key.as_deref() != Some("block.minecraft.ominous_banner") {
return;
}
if components.get_string("minecraft:rarity") == Some("uncommon") {
components.take("minecraft:rarity");
report_loss(
VERSION,
LossKind::FingerprintCollapse,
Severity::Approximated,
"ominous banner uncommon rarity may be forward-added or user-authored; removed it for the legacy preimage",
);
}
}
pub fn register(reg: &mut RegistryBuilder) {
reg.tile_entity.add_converter_for_id(
"minecraft:banner",
VERSION,
0,
Box::new(|data, _from, _to| {
convert_components(data.get_map_mut("components"));
}),
);
// Reverse of TILE_ENTITY minecraft:banner (V4054.java:14-20).
reg.tile_entity.add_reverse_converter_for_id(
"minecraft:banner",
VERSION,
0,
Box::new(|data, _from, _to| {
reverse_convert_components(data.get_map_mut("components"));
}),
);
reg.item_stack.add_converter_for_id(
"minecraft:white_banner",
VERSION,
0,
Box::new(|data, _from, _to| {
convert_components(data.get_map_mut("components"));
}),
);
// Reverse of ITEM_STACK minecraft:white_banner (V4054.java:21-27).
reg.item_stack.add_reverse_converter_for_id(
"minecraft:white_banner",
VERSION,
0,
Box::new(|data, _from, _to| {
reverse_convert_components(data.get_map_mut("components"));
}),
);
}