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
//! V105 (V15W32C + 1) — legacy `minecraft:spawn_egg` ITEM_STACK converter
//! (V105.java:15-46).
//!
//! For a legacy spawn egg: read the short `Damage`, zero it if non-zero, and
//! when `tag.EntityTag` has no String `id`, resolve the legacy entity name from
//! the damage value (`HelperSpawnEggNameV105.getSpawnNameFromId`, mirrored by
//! `flattening::get_spawn_name_from_id`) and write it back through tag/EntityTag.
//! Nothing else from V105.java is in scope.
use crate::nbt::NbtMap;
use super::super::flattening::get_spawn_name_from_id;
use super::super::loss::{report_loss, LossKind, Severity};
use super::super::registry::RegistryBuilder;
use super::super::types::MapExt;
const VERSION: i32 = 105;
pub fn register(reg: &mut RegistryBuilder) {
reg.item_stack.add_converter_for_id(
"minecraft:spawn_egg",
VERSION,
0,
Box::new(|data, _from, _to| {
// Java getShort("Damage") defaults to 0 when absent.
let damage = data.get_i32("Damage").unwrap_or(0) as i16;
if damage != 0 {
data.set_short("Damage", 0);
}
// Only act when EntityTag lacks a String `id`. Java builds empty
// tag/EntityTag maps but writes them back only if a name resolves,
// so a missing/unresolvable damage leaves the item untouched.
let has_id = data
.get_map("tag")
.and_then(|tag| tag.get_map("EntityTag"))
.and_then(|et| et.get_string("id"))
.is_some();
if has_id {
return;
}
let Some(converted) = get_spawn_name_from_id(damage) else {
return;
};
// tag.getOrCreate -> EntityTag.getOrCreate -> set id; then write back.
if data.get_map("tag").is_none() {
data.set_map("tag", NbtMap::new());
}
let tag = data.get_map_mut("tag").unwrap();
if tag.get_map("EntityTag").is_none() {
tag.set_map("EntityTag", NbtMap::new());
}
let entity_tag = tag.get_map_mut("EntityTag").unwrap();
entity_tag.set_string("id", converted);
}),
);
// Reverse of the spawn-egg converter (new -> old). The forward zeroed the
// legacy short `Damage` (which encoded the egg's entity type) and, when
// `tag.EntityTag` lacked a String `id`, stamped the entity name resolved
// from that damage. Going backwards we read `tag.EntityTag.id` and restore
// the legacy `Damage` discriminator via the inverse of
// `HelperSpawnEggNameV105.ID_TO_STRING` (each name appears exactly once in
// that table, so name -> damage is injective => lossless for the round
// trip). We do NOT strip `EntityTag.id`: the legacy format could legitimately
// carry it (the forward only *added* it when absent), and removing it could
// drop user data; restoring `Damage` is the meaningful legacy discriminator.
//
// Lossy edge (no report needed, nothing to act on): if the original legacy
// `Damage` was a value outside the table, the forward zeroed it and wrote no
// `id`, so there is no surviving signal here to reconstruct it; we leave the
// item untouched.
reg.item_stack.add_reverse_converter_for_id(
"minecraft:spawn_egg",
VERSION,
0,
Box::new(|data, _from, _to| {
let Some(name) = data
.get_map("tag")
.and_then(|tag| tag.get_map("EntityTag"))
.and_then(|et| et.get_string("id"))
else {
return;
};
// Inverse of SPAWN_EGG_NAME_BY_ID (flattening/data.rs:2835) /
// HelperSpawnEggNameV105.ID_TO_STRING: entity name -> legacy damage.
let damage: i16 = match name {
"Item" => 1,
"XPOrb" => 2,
"ThrownEgg" => 7,
"LeashKnot" => 8,
"Painting" => 9,
"Arrow" => 10,
"Snowball" => 11,
"Fireball" => 12,
"SmallFireball" => 13,
"ThrownEnderpearl" => 14,
"EyeOfEnderSignal" => 15,
"ThrownPotion" => 16,
"ThrownExpBottle" => 17,
"ItemFrame" => 18,
"WitherSkull" => 19,
"PrimedTnt" => 20,
"FallingSand" => 21,
"FireworksRocketEntity" => 22,
"TippedArrow" => 23,
"SpectralArrow" => 24,
"ShulkerBullet" => 25,
"DragonFireball" => 26,
"ArmorStand" => 30,
"Boat" => 41,
"MinecartRideable" => 42,
"MinecartChest" => 43,
"MinecartFurnace" => 44,
"MinecartTNT" => 45,
"MinecartHopper" => 46,
"MinecartSpawner" => 47,
"MinecartCommandBlock" => 40,
"Creeper" => 50,
"Skeleton" => 51,
"Spider" => 52,
"Giant" => 53,
"Zombie" => 54,
"Slime" => 55,
"Ghast" => 56,
"PigZombie" => 57,
"Enderman" => 58,
"CaveSpider" => 59,
"Silverfish" => 60,
"Blaze" => 61,
"LavaSlime" => 62,
"EnderDragon" => 63,
"WitherBoss" => 64,
"Bat" => 65,
"Witch" => 66,
"Endermite" => 67,
"Guardian" => 68,
"Shulker" => 69,
"Pig" => 90,
"Sheep" => 91,
"Cow" => 92,
"Chicken" => 93,
"Squid" => 94,
"Wolf" => 95,
"MushroomCow" => 96,
"SnowMan" => 97,
"Ozelot" => 98,
"VillagerGolem" => 99,
"EntityHorse" => 100,
"Rabbit" => 101,
"Villager" => 120,
"EnderCrystal" => 200,
// Name not in the legacy table (e.g. a modern entity id):
// there is no legacy damage encoding for it, so leave Damage
// alone rather than fabricate one, but do not silently ignore
// the unsupported downgrade.
_ => {
report_loss(
VERSION,
LossKind::UnsupportedInTarget,
Severity::Loss,
format!(
"spawn_egg EntityTag.id '{name}' has no pre-V105 legacy Damage encoding"
),
);
return;
}
};
data.set_short("Damage", damage);
}),
);
}