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
//! V106 (15w32c+2) — old-format spawner migration + first spawner walker; cites V106.java.
//!
//! Ports both UNTAGGED_SPAWNER registrations from V106.java:
//! - structure converter: legacy `EntityId` -> `SpawnData.id`, and each
//! `SpawnPotentials[]` entry's `Type`/`Properties` -> standard `Entity` format.
//! - structure walker: recurse `SpawnData` and each `SpawnPotentials[].Entity`
//! as ENTITY (the first spawner walker registered in Java).
//! No other (LEVEL/CHUNK/etc.) registrations exist in this file.
use std::sync::Arc;
use crate::nbt::{NbtMap, NbtValue};
use super::super::loss::{report_loss, LossKind, Severity};
use super::super::registry::RegistryBuilder;
use super::super::types::{MapExt, ValueExt};
use super::super::walker::convert_list_path;
const VERSION: i32 = 106;
pub fn register(reg: &mut RegistryBuilder) {
// V106.java:19-65 — old-format spawner migration. Not guarded on id: a
// spawner can be a minecart spawner, not only a tile entity (this is only
// invoked from data walkers).
reg.untagged_spawner.add_structure_converter(
VERSION,
0,
Box::new(|data, _from, _to| {
if let Some(entity_id) = data.get_string("EntityId").map(str::to_string) {
data.take("EntityId");
if data.get_map("SpawnData").is_none() {
data.set_map("SpawnData", NbtMap::new());
}
let spawn_data = data.get_map_mut("SpawnData").unwrap();
spawn_data.set_string(
"id",
if entity_id.is_empty() {
"Pig"
} else {
&entity_id
},
);
}
if let Some(spawn_potentials) = data.get_list_mut("SpawnPotentials") {
for el in spawn_potentials.iter_mut() {
let Some(spawn) = el.as_compound_mut() else {
continue;
};
let Some(spawn_type) = spawn.get_string("Type").map(str::to_string) else {
continue;
};
spawn.take("Type");
// Java: getMap("Properties") is null for a missing OR
// non-map value; only remove when it was actually a map.
let mut properties = match spawn.get_map("Properties") {
Some(_) => match spawn.take("Properties") {
Some(NbtValue::Compound(m)) => m,
_ => NbtMap::new(),
},
None => NbtMap::new(),
};
properties.set_string("id", &spawn_type);
spawn.set_map("Entity", properties);
}
}
}),
);
// Reverse of V106.java:19-65 — restore the legacy spawner shape (new -> old).
// The walker (registered below) descends FIRST in reverse, so by the time
// this runs the ENTITY children inside `SpawnData` and each
// `SpawnPotentials[].Entity` are already downgraded; we only restructure the
// spawner's own container fields here. Lossless (bucket B): the forward moves
// are 1:1 field relocations whose new shape uniquely encodes the old shape.
reg.untagged_spawner.add_reverse_converter(
VERSION,
0,
Box::new(|data, _from, _to| {
// Inverse of EntityId -> SpawnData.id: pull the id back out to the
// top-level `EntityId` string and drop it from `SpawnData`.
// (Forward maps an empty EntityId to "Pig"; an empty source is a
// degenerate spawner, so restoring EntityId = id — i.e. "Pig" — is the
// canonical preimage and not treated as loss.)
if let Some(spawn_data) = data.get_map_mut("SpawnData") {
if let Some(id) = spawn_data.get_string("id").map(str::to_string) {
spawn_data.take("id");
data.set_string("EntityId", &id);
}
}
// Inverse of each SpawnPotentials[] entry's Type/Properties -> Entity:
// split `Entity` back into the `Type` string (its id) and the
// remaining map as `Properties`. The forward only created `Properties`
// from a pre-existing map, so we only re-add it when the leftover map
// is non-empty, matching the old shape.
if let Some(spawn_potentials) = data.get_list_mut("SpawnPotentials") {
for el in spawn_potentials.iter_mut() {
let Some(spawn) = el.as_compound_mut() else {
continue;
};
let Some(entity) = spawn.get_map("Entity") else {
continue;
};
let Some(spawn_type) = entity.get_string("id").map(str::to_string) else {
report_loss(
VERSION,
LossKind::UnsupportedInTarget,
Severity::Loss,
"SpawnPotentials[].Entity is missing id; cannot restore legacy Type",
);
continue;
};
let Some(NbtValue::Compound(mut entity)) = spawn.take("Entity") else {
continue;
};
entity.take("id");
spawn.set_string("Type", &spawn_type);
if !entity.is_empty() {
spawn.set_map("Properties", entity);
}
}
}
}),
);
// V106.java:67-79 — first spawner walker: recurse SpawnData and each
// SpawnPotentials[].Entity as ENTITY.
reg.untagged_spawner.add_structure_walker(
VERSION,
0,
Arc::new(|reg, data, from, to| {
convert_list_path(
reg,
®.entity,
data,
"SpawnPotentials",
"Entity",
from,
to,
);
super::super::walker::convert(reg, ®.entity, data, "SpawnData", from, to);
}),
);
}