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
//! V3685 (23w42a+1, `V23W42A + 1` = 3685) — schematic-relevant subset of
//! `V3685.java`.
//!
//! Arrow/trident entities gained an `item` itemstack. Ported:
//! * ENTITY `minecraft:trident` converter: rename `Trident` -> `item`.
//! * ENTITY `minecraft:arrow` converter: synthesize `item = {id, Count:1}` where
//! id is `minecraft:tipped_arrow` when `Potion != minecraft:empty`, else
//! `minecraft:arrow`.
//! * ENTITY `minecraft:spectral_arrow` converter: synthesize
//! `item = {id:"minecraft:spectral_arrow", Count:1}`.
//! * Walkers for trident/spectral_arrow/arrow: `inBlockState` -> BLOCK_STATE and
//! `item` -> ITEM_STACK.
//!
//! Nothing non-schematic here (all are schematic entities).
use std::sync::Arc;
use crate::nbt::NbtMap;
use super::super::loss::{report_loss, LossKind, Severity};
use super::super::registry::RegistryBuilder;
use super::super::types::MapExt;
use super::super::walker::{convert, items};
const VERSION: i32 = 3685;
/// `getType` (V3685.java:16-18): empty potion -> arrow, else tipped_arrow.
fn arrow_type(arrow: &NbtMap) -> &'static str {
let potion = arrow.get_string("Potion").unwrap_or("minecraft:empty");
if potion == "minecraft:empty" {
"minecraft:arrow"
} else {
"minecraft:tipped_arrow"
}
}
/// `createItem` (V3685.java:20-27): `{id, Count}`.
fn create_item(id: &str, count: i32) -> NbtMap {
let mut ret = NbtMap::new();
ret.set_string("id", id);
ret.set_i32("Count", count);
ret
}
fn is_synthesized_item(data: &NbtMap, expected_id: &str) -> bool {
let Some(item) = data.get_map("item") else {
return true;
};
item.get_string("id") == Some(expected_id)
&& item.get_i32("Count").unwrap_or(1) == 1
&& item
.keys()
.into_iter()
.all(|key| key == "id" || key == "Count")
}
/// `registerArrowEntity` (V3685.java:29-33): inBlockState -> BLOCK_STATE,
/// item -> ITEM_STACK.
fn register_arrow_entity(reg: &mut RegistryBuilder, id: &'static str) {
reg.entity.add_walker(
VERSION,
0,
id,
Arc::new(|reg, data, from, to| {
convert(reg, ®.block_state, data, "inBlockState", from, to)
}),
);
reg.entity.add_walker(VERSION, 0, id, items(&["item"]));
}
pub fn register(reg: &mut RegistryBuilder) {
reg.entity.add_converter_for_id(
"minecraft:trident",
VERSION,
0,
Box::new(|data, _from, _to| {
data.rename_key("Trident", "item");
}),
);
// Reverse: undo the manual `Trident` -> `item` rename (lossless, bucket A).
// This rename is done via add_converter_for_id (not map_renamer), so it is
// NOT auto-inverted by the engine — invert it explicitly. Matches the NEW key
// (`item`).
reg.entity.add_reverse_converter_for_id(
"minecraft:trident",
VERSION,
0,
Box::new(|data, _from, _to| {
data.rename_key("item", "Trident");
}),
);
reg.entity.add_converter_for_id(
"minecraft:arrow",
VERSION,
0,
Box::new(|data, _from, _to| {
let item = create_item(arrow_type(data), 1);
data.set_map("item", item);
}),
);
// Reverse: drop the synthesized `item` (bucket D additive). The forward derives
// it entirely from the still-present `Potion` field (arrow vs tipped_arrow), so
// removal is exact — no loss.
reg.entity.add_reverse_converter_for_id(
"minecraft:arrow",
VERSION,
0,
Box::new(|data, _from, _to| {
let expected = arrow_type(data);
if !is_synthesized_item(data, expected) {
report_loss(
VERSION,
LossKind::UnsupportedInTarget,
Severity::Loss,
"arrow item field contains modern-only state with no pre-23w42a entity representation",
);
}
data.take("item");
}),
);
reg.entity.add_converter_for_id(
"minecraft:spectral_arrow",
VERSION,
0,
Box::new(|data, _from, _to| {
let item = create_item("minecraft:spectral_arrow", 1);
data.set_map("item", item);
}),
);
// Reverse: drop the synthesized constant `item` (bucket D additive). The forward
// adds a fixed `{id:"minecraft:spectral_arrow", Count:1}` that the old format
// never carried, so removal is exact — no loss.
reg.entity.add_reverse_converter_for_id(
"minecraft:spectral_arrow",
VERSION,
0,
Box::new(|data, _from, _to| {
if !is_synthesized_item(data, "minecraft:spectral_arrow") {
report_loss(
VERSION,
LossKind::UnsupportedInTarget,
Severity::Loss,
"spectral_arrow item field contains modern-only state with no pre-23w42a entity representation",
);
}
data.take("item");
}),
);
register_arrow_entity(reg, "minecraft:trident");
register_arrow_entity(reg, "minecraft:spectral_arrow");
register_arrow_entity(reg, "minecraft:arrow");
}