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
//! V3322 (23w04a + 1) — schematic-relevant subset of `V3322.java`.
//!
//! The "entity effect" fix (V3322.java:26-66): for the effect lists `Effects`,
//! `ActiveEffects`, and `CustomPotionEffects`, each effect entry that carries a
//! `FactorCalculationData` map gets `effect_changed_timestamp` removed and
//! `ticks_active` written as `effect_changed_timestamp - Duration` (both default
//! to -1 when absent; `Duration` is read off the effect element, the timestamp
//! off `FactorCalculationData`).
//!
//! The Java file registers `entityEffectFix` on both PLAYER and ENTITY; only the
//! ENTITY registration is schematic-relevant, so the PLAYER one is skipped.
//!
//! It also registers an ITEM_STACK converter (V3322.java:68-80): when the item
//! `id` is one of potion/splash_potion/lingering_potion/tipped_arrow, the same
//! effect-list fix is applied to `tag.CustomPotionEffects`.
//!
//! VERSION = MCVersions.V23W04A (3321) + 1 = 3322.
use crate::nbt::NbtMap;
use super::super::registry::RegistryBuilder;
use super::super::types::{MapExt, ValueExt};
const VERSION: i32 = 3322;
/// Item ids whose `tag.CustomPotionEffects` get the fix (V3322.java:17-24).
const EFFECT_ITEM_TYPES: &[&str] = &[
"minecraft:potion",
"minecraft:splash_potion",
"minecraft:lingering_potion",
"minecraft:tipped_arrow",
];
/// Port of `updateEffectList(root, path)` (V3322.java:26-52).
fn update_effect_list(root: &mut NbtMap, path: &str) {
let effects = match root.get_list_mut(path) {
Some(list) => list,
None => return,
};
for el in effects.iter_mut() {
// getList(path, MAP) only yields map elements; non-maps are ignored.
let data = match el.as_compound_mut() {
Some(map) => map,
None => continue,
};
// Duration is read off the effect element (default -1).
let duration = data.get_i32("Duration").unwrap_or(-1);
let factor_data = match data.get_map_mut("FactorCalculationData") {
Some(map) => map,
None => continue,
};
let timestamp = factor_data
.get_i32("effect_changed_timestamp")
.unwrap_or(-1);
factor_data.take("effect_changed_timestamp");
let ticks_active = timestamp - duration;
factor_data.set_i32("ticks_active", ticks_active);
}
}
/// Inverse of `update_effect_list`: restore `effect_changed_timestamp` and drop
/// `ticks_active` on every effect carrying `FactorCalculationData`.
///
/// Forward set `ticks_active = effect_changed_timestamp - Duration` (both default
/// -1) and removed `effect_changed_timestamp`. `Duration` lives on the effect
/// element and is untouched by the forward, so the original timestamp is exactly
/// recoverable as `ticks_active + Duration` — lossless (no `report_loss`).
fn restore_effect_list(root: &mut NbtMap, path: &str) {
let effects = match root.get_list_mut(path) {
Some(list) => list,
None => return,
};
for el in effects.iter_mut() {
let data = match el.as_compound_mut() {
Some(map) => map,
None => continue,
};
// Duration is read off the effect element (default -1), mirroring forward.
let duration = data.get_i32("Duration").unwrap_or(-1);
let factor_data = match data.get_map_mut("FactorCalculationData") {
Some(map) => map,
None => continue,
};
let ticks_active = factor_data.get_i32("ticks_active").unwrap_or(-1);
factor_data.take("ticks_active");
let timestamp = ticks_active + duration;
factor_data.set_i32("effect_changed_timestamp", timestamp);
}
}
pub fn register(reg: &mut RegistryBuilder) {
// ENTITY: Effects / ActiveEffects / CustomPotionEffects effect-list fix.
// (The identical PLAYER registration is non-schematic and skipped.)
reg.entity.add_structure_converter(
VERSION,
0,
Box::new(|data: &mut NbtMap, _from, _to| {
update_effect_list(data, "Effects");
update_effect_list(data, "ActiveEffects");
update_effect_list(data, "CustomPotionEffects");
}),
);
// Reverse: restore effect_changed_timestamp / drop ticks_active (lossless).
reg.entity.add_reverse_converter(
VERSION,
0,
Box::new(|data: &mut NbtMap, _from, _to| {
restore_effect_list(data, "Effects");
restore_effect_list(data, "ActiveEffects");
restore_effect_list(data, "CustomPotionEffects");
}),
);
// ITEM_STACK: for potion-like items, fix tag.CustomPotionEffects.
reg.item_stack.add_structure_converter(
VERSION,
0,
Box::new(|data: &mut NbtMap, _from, _to| {
let is_effect_item = data
.get_string("id")
.map(|id| EFFECT_ITEM_TYPES.contains(&id))
.unwrap_or(false);
if !is_effect_item {
return;
}
if let Some(tag) = data.get_map_mut("tag") {
update_effect_list(tag, "CustomPotionEffects");
}
}),
);
// Reverse: for potion-like items, restore tag.CustomPotionEffects (lossless).
reg.item_stack.add_reverse_converter(
VERSION,
0,
Box::new(|data: &mut NbtMap, _from, _to| {
let is_effect_item = data
.get_string("id")
.map(|id| EFFECT_ITEM_TYPES.contains(&id))
.unwrap_or(false);
if !is_effect_item {
return;
}
if let Some(tag) = data.get_map_mut("tag") {
restore_effect_list(tag, "CustomPotionEffects");
}
}),
);
}