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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! V4314 (25w06a + 1) — schematic-relevant subset of `V4314.java`.
//!
//! ENTITY converters that collapse split `X/Y/Z` block-position fields into a
//! single int-array field, plus a few field renames:
//! * all living entities: `SleepingX/Y/Z` -> `sleeping_pos` (V4314.java:933-941,
//! also registered on PLAYER which we skip).
//! * `minecraft:vex`: `BoundX/Y/Z` -> `bound_pos`, `LifeTicks` -> `life_ticks`.
//! * `minecraft:phantom`: `AX/AY/AZ` -> `anchor_pos`, `Size` -> `size`.
//! * `minecraft:turtle`: drop `TravelPosX/Y/Z`, `HomePosX/Y/Z` -> `home_pos`,
//! `HasEgg` -> `has_egg`.
//! * attached-block entities (item_frame / glow_item_frame / painting /
//! leash_knot): `TileX/Y/Z` -> `block_pos`.
//!
//! Skipped (non-schematic): both PLAYER structure converters — the
//! `livingEntityConverter` registered on PLAYER, and the spawn/respawn +
//! `enteredNetherPosition` migration (V4314.java:941-978) — PLAYER never appears
//! in a schematic file.
//!
//! `convertBlockPosition` is V4309's shared helper (V4309.java:704-723), inlined
//! here because V4309 itself only touches non-schematic SAVED_DATA types and so
//! is not ported.
//!
//! VERSION = V25W06A(4313) + 1.
use crate::nbt::{NbtMap, NbtValue};
use super::super::loss::{report_loss, LossKind, Severity};
use super::super::registry::RegistryBuilder;
use super::super::types::MapExt;
const VERSION: i32 = 4314;
/// `V4309.makeBlockPosition` + `convertBlockPosition` (V4309.java:704-723): read
/// three numeric coords, remove them, and write a 3-int array at `to_path`. A
/// missing coord aborts (leaving the originals in place).
fn convert_block_position(
data: &mut NbtMap,
x_path: &str,
y_path: &str,
z_path: &str,
to_path: &str,
) {
let x = match data.get_i32(x_path) {
Some(v) => v,
None => return,
};
let y = match data.get_i32(y_path) {
Some(v) => v,
None => return,
};
let z = match data.get_i32(z_path) {
Some(v) => v,
None => return,
};
data.take(x_path);
data.take(y_path);
data.take(z_path);
data.set_generic(to_path, NbtValue::IntArray(vec![x, y, z]));
}
/// Inverse of `convert_block_position`: if `data[to_path]` is an `int[3]`
/// `[x,y,z]`, remove it and write the three split `x_path/y_path/z_path` ints.
/// Lossless (bucket B) — the array uniquely encodes the three ints the old split
/// fields held; a missing/wrong-length array aborts (leaving data untouched).
fn unconvert_block_position(
data: &mut NbtMap,
x_path: &str,
y_path: &str,
z_path: &str,
to_path: &str,
) {
let (x, y, z) = match data.get(to_path) {
Some(NbtValue::IntArray(v)) if v.len() == 3 => (v[0], v[1], v[2]),
_ => return,
};
data.take(to_path);
data.set_i32(x_path, x);
data.set_i32(y_path, y);
data.set_i32(z_path, z);
}
pub fn register(reg: &mut RegistryBuilder) {
// Living-entity sleeping position (registered on every ENTITY).
reg.entity.add_structure_converter(
VERSION,
0,
Box::new(|data, _from, _to| {
convert_block_position(data, "SleepingX", "SleepingY", "SleepingZ", "sleeping_pos");
}),
);
// Reverse (lossless, bucket B): sleeping_pos int[3] -> SleepingX/Y/Z.
reg.entity.add_reverse_converter(
VERSION,
0,
Box::new(|data, _from, _to| {
unconvert_block_position(data, "SleepingX", "SleepingY", "SleepingZ", "sleeping_pos");
}),
);
reg.entity.add_converter_for_id(
"minecraft:vex",
VERSION,
0,
Box::new(|data, _from, _to| {
convert_block_position(data, "BoundX", "BoundY", "BoundZ", "bound_pos");
data.rename_key("LifeTicks", "life_ticks");
}),
);
// Reverse (lossless, bucket B): life_ticks -> LifeTicks, bound_pos int[3] ->
// BoundX/Y/Z. (Both field renames here are inline closure renames, not
// map_renamer registrations, so they need explicit inversion.)
reg.entity.add_reverse_converter_for_id(
"minecraft:vex",
VERSION,
0,
Box::new(|data, _from, _to| {
data.rename_key("life_ticks", "LifeTicks");
unconvert_block_position(data, "BoundX", "BoundY", "BoundZ", "bound_pos");
}),
);
reg.entity.add_converter_for_id(
"minecraft:phantom",
VERSION,
0,
Box::new(|data, _from, _to| {
convert_block_position(data, "AX", "AY", "AZ", "anchor_pos");
data.rename_key("Size", "size");
}),
);
// Reverse (lossless, bucket B): size -> Size, anchor_pos int[3] -> AX/AY/AZ.
reg.entity.add_reverse_converter_for_id(
"minecraft:phantom",
VERSION,
0,
Box::new(|data, _from, _to| {
data.rename_key("size", "Size");
unconvert_block_position(data, "AX", "AY", "AZ", "anchor_pos");
}),
);
reg.entity.add_converter_for_id(
"minecraft:turtle",
VERSION,
0,
Box::new(|data, _from, _to| {
data.take("TravelPosX");
data.take("TravelPosY");
data.take("TravelPosZ");
convert_block_position(data, "HomePosX", "HomePosY", "HomePosZ", "home_pos");
data.rename_key("HasEgg", "has_egg");
}),
);
// Reverse: has_egg -> HasEgg (lossless), home_pos int[3] -> HomePosX/Y/Z
// (lossless). TravelPosX/Y/Z (the turtle's travel target) was DROPPED by the
// forward with no modern representation, so it cannot be restored — bucket C,
// report_loss. The old turtle defaults a missing travel target, so we add
// nothing back.
reg.entity.add_reverse_converter_for_id(
"minecraft:turtle",
VERSION,
0,
Box::new(|data, _from, _to| {
data.rename_key("has_egg", "HasEgg");
unconvert_block_position(data, "HomePosX", "HomePosY", "HomePosZ", "home_pos");
report_loss(
VERSION,
LossKind::UnsupportedInTarget,
Severity::Loss,
"turtle TravelPosX/Y/Z was dropped on upgrade and cannot be restored",
);
}),
);
// Attached-block entities: TileX/Y/Z -> block_pos.
for id in [
"minecraft:item_frame",
"minecraft:glow_item_frame",
"minecraft:painting",
"minecraft:leash_knot",
] {
reg.entity.add_converter_for_id(
id,
VERSION,
0,
Box::new(|data, _from, _to| {
convert_block_position(data, "TileX", "TileY", "TileZ", "block_pos");
}),
);
// Reverse (lossless, bucket B): block_pos int[3] -> TileX/Y/Z.
reg.entity.add_reverse_converter_for_id(
id,
VERSION,
0,
Box::new(|data, _from, _to| {
unconvert_block_position(data, "TileX", "TileY", "TileZ", "block_pos");
}),
);
}
}