1use super::*;
2use crate::entity::clamp_loaded_entity_position;
3
4impl ChunkStorage {
5 pub(super) fn entities_to_persistent(entities: &[SharedEntity]) -> Vec<PersistentEntity> {
6 let mut visited = FxHashSet::default();
7 entities
8 .iter()
9 .filter(|entity| !entity.is_passenger())
10 .filter_map(|entity| {
11 Self::entity_to_persistent(entity, &mut visited, EntityPersistenceMode::ChunkSave)
12 })
13 .collect()
14 }
15
16 pub(crate) fn entity_tree_to_persistent(entity: &SharedEntity) -> Option<PersistentEntity> {
17 let mut visited = FxHashSet::default();
18 Self::entity_to_persistent(entity, &mut visited, EntityPersistenceMode::ChunkSave)
19 }
20
21 pub(crate) fn entity_to_dimension_transition_persistent(
22 entity: &SharedEntity,
23 ) -> Option<PersistentEntity> {
24 let mut visited = FxHashSet::default();
25 Self::entity_to_persistent(
26 entity,
27 &mut visited,
28 EntityPersistenceMode::DimensionTransition,
29 )
30 }
31
32 pub(super) fn custom_name_to_persistent(custom_name: Option<&TextComponent>) -> Vec<u8> {
33 let Some(custom_name) = custom_name else {
34 return Vec::new();
35 };
36
37 let mut root = NbtCompound::new();
38 root.insert("CustomName", custom_name.to_codec_nbt());
39 let mut bytes = Vec::new();
40 root.write(&mut bytes);
41 bytes
42 }
43
44 pub(super) fn custom_name_from_persistent(
45 bytes: &[u8],
46 uuid: uuid::Uuid,
47 ) -> Option<TextComponent> {
48 if bytes.is_empty() {
49 return None;
50 }
51
52 let Ok(root) = read_borrowed_compound(&mut Cursor::new(bytes)) else {
53 tracing::warn!(
54 ?uuid,
55 "Failed to parse entity custom name NBT, defaulting to no custom name"
56 );
57 return None;
58 };
59 let root = simdnbt::borrow::NbtCompound::from(&root);
60 let tag = root.get("CustomName")?;
61 let custom_name = TextComponent::from_nbt(&tag.to_owned());
62 if custom_name.is_none() {
63 tracing::warn!(
64 ?uuid,
65 "Failed to decode entity custom name, defaulting to no custom name"
66 );
67 return None;
68 }
69 custom_name
70 }
71
72 pub(super) fn compound_to_persistent(compound: &NbtCompound) -> Vec<u8> {
73 if compound.is_empty() {
74 return Vec::new();
75 }
76
77 let mut bytes = Vec::new();
78 compound.write(&mut bytes);
79 bytes
80 }
81
82 pub(super) fn compound_from_persistent(bytes: &[u8], uuid: uuid::Uuid) -> NbtCompound {
83 if bytes.is_empty() {
84 return NbtCompound::new();
85 }
86
87 let Ok(compound) = read_borrowed_compound(&mut Cursor::new(bytes)) else {
88 tracing::warn!(
89 ?uuid,
90 "Failed to parse entity custom data NBT, defaulting to empty custom data"
91 );
92 return NbtCompound::new();
93 };
94 simdnbt::borrow::NbtCompound::from(&compound).to_owned()
95 }
96
97 pub(super) fn save_data_from_persistent(
98 persistent: &PersistentEntity,
99 uuid: uuid::Uuid,
100 ) -> EntityBaseSaveData {
101 EntityBaseSaveData {
102 air_supply: persistent.air_supply,
103 portal_cooldown: persistent.portal_cooldown,
104 no_gravity: persistent.no_gravity,
105 invulnerable: persistent.invulnerable,
106 custom_name: Self::custom_name_from_persistent(&persistent.custom_name_nbt, uuid),
107 custom_name_visible: persistent.custom_name_visible,
108 silent: persistent.silent,
109 glowing: persistent.glowing,
110 tags: persistent
111 .tags
112 .iter()
113 .take(MAX_ENTITY_TAGS)
114 .cloned()
115 .collect(),
116 custom_data: Self::compound_from_persistent(&persistent.custom_data_nbt, uuid),
117 }
118 }
119
120 pub(super) fn entity_to_persistent(
121 entity: &SharedEntity,
122 visited: &mut FxHashSet<i32>,
123 mode: EntityPersistenceMode,
124 ) -> Option<PersistentEntity> {
125 if !Self::entity_should_persist(entity.as_ref(), mode) {
126 return None;
127 }
128
129 if !visited.insert(entity.id()) {
130 tracing::warn!(
131 uuid = ?entity.uuid(),
132 "Entity passenger tree contains duplicate entity id {}, skipping duplicate save",
133 entity.id()
134 );
135 return None;
136 }
137
138 let pos = entity.position();
139 let stored_pos = if let Some(vehicle) = entity.vehicle() {
140 let vehicle_pos = vehicle.position();
141 DVec3::new(vehicle_pos.x, pos.y, vehicle_pos.z)
142 } else {
143 pos
144 };
145 let vel = entity.velocity();
146 let (yaw, pitch) = entity.rotation();
147 let fire_freeze = entity.fire_freeze_state();
148 let save_data = entity.base().save_data();
149
150 if !stored_pos.x.is_finite() || !stored_pos.y.is_finite() || !stored_pos.z.is_finite() {
151 tracing::warn!(
152 uuid = ?entity.uuid(),
153 "Entity has non-finite position {:?}, skipping save",
154 stored_pos
155 );
156 return None;
157 }
158
159 let mut nbt = NbtCompound::new();
160 entity.save_additional(&mut nbt);
161 let mut nbt_bytes = Vec::new();
162 nbt.write(&mut nbt_bytes);
163
164 let passengers = entity
165 .passengers()
166 .iter()
167 .filter_map(|passenger| Self::entity_to_persistent(passenger, visited, mode))
168 .collect();
169
170 Some(PersistentEntity {
171 entity_type: entity.entity_type().key.clone(),
172 uuid: *entity.uuid().as_bytes(),
173 pos: [stored_pos.x, stored_pos.y, stored_pos.z],
174 motion: [vel.x, vel.y, vel.z],
175 rotation: [yaw, pitch],
176 fall_distance: entity.fall_distance(),
177 remaining_fire_ticks: fire_freeze.remaining_fire_ticks(),
178 ticks_frozen: fire_freeze.ticks_frozen(),
179 is_in_powder_snow: fire_freeze.is_in_powder_snow(),
180 was_in_powder_snow: fire_freeze.was_in_powder_snow(),
181 has_visual_fire: fire_freeze.has_visual_fire(),
182 on_ground: entity.on_ground(),
183 no_gravity: save_data.no_gravity,
184 invulnerable: save_data.invulnerable,
185 air_supply: save_data.air_supply,
186 portal_cooldown: save_data.portal_cooldown,
187 custom_name_nbt: Self::custom_name_to_persistent(save_data.custom_name.as_ref()),
188 custom_name_visible: save_data.custom_name_visible,
189 silent: save_data.silent,
190 glowing: save_data.glowing,
191 tags: save_data.tags.iter().cloned().collect(),
192 custom_data_nbt: Self::compound_to_persistent(&save_data.custom_data),
193 nbt_data: nbt_bytes,
194 passengers,
195 })
196 }
197
198 pub(super) fn entity_should_save(entity: &dyn Entity) -> bool {
199 (!entity.is_removed()
200 || entity
201 .removal_reason()
202 .is_some_and(RemovalReason::should_save))
203 && entity.entity_type().can_serialize
204 }
205
206 pub(super) fn entity_should_persist(entity: &dyn Entity, mode: EntityPersistenceMode) -> bool {
207 match mode {
208 EntityPersistenceMode::ChunkSave => Self::entity_should_save(entity),
209 EntityPersistenceMode::DimensionTransition => !entity.is_removed(),
210 }
211 }
212
213 pub(super) fn persistent_block_entity_pos(
215 persistent: &PersistentBlockEntity,
216 chunk_pos: ChunkPos,
217 ) -> BlockPos {
218 let abs_x = chunk_pos.0.x * 16 + i32::from(persistent.x);
219 let abs_z = chunk_pos.0.y * 16 + i32::from(persistent.z);
220 BlockPos::new(abs_x, i32::from(persistent.y), abs_z)
221 }
222
223 pub(super) fn persistent_to_block_entity(
225 persistent: &PersistentBlockEntity,
226 chunk_pos: ChunkPos,
227 chunk: FullChunkRef<'_>,
228 ) -> Option<SharedBlockEntity> {
229 let pos = Self::persistent_block_entity_pos(persistent, chunk_pos);
230 let state = chunk.get_block_state(pos);
231 Self::persistent_to_block_entity_at(persistent, pos, chunk.level_weak(), state)
232 }
233
234 pub(super) fn persistent_to_block_entity_at(
235 persistent: &PersistentBlockEntity,
236 pos: BlockPos,
237 level: Weak<World>,
238 state: BlockStateId,
239 ) -> Option<SharedBlockEntity> {
240 let block_entity_type_key = persistent.entity_type.as_ref()?;
242 let block_entity_type = REGISTRY.block_entity_types.by_key(block_entity_type_key)?;
243 if !block_entity_type.is_valid(state.get_block()) {
244 log::warn!(
245 "Skipping block entity {} at {pos:?}: block {} does not accept that type",
246 block_entity_type.key,
247 state.get_block().key,
248 );
249 return None;
250 }
251
252 if persistent.nbt_data.is_empty() {
254 Some(BLOCK_ENTITIES.create_or_unimplemented(block_entity_type, level, pos, state))
256 } else {
257 let Ok(nbt) = read_borrowed_compound(&mut Cursor::new(&persistent.nbt_data)) else {
259 log::warn!(
260 "Skipping block entity {} at {pos:?}: malformed NBT",
261 block_entity_type.key,
262 );
263 return None;
264 };
265
266 Some(BLOCK_ENTITIES.create_and_load_or_unimplemented(
268 block_entity_type,
269 level,
270 pos,
271 state,
272 &nbt,
273 ))
274 }
275 }
276
277 pub(crate) fn persistent_to_entity_tree_at_level(
279 persistent: &PersistentEntity,
280 chunk_pos: ChunkPos,
281 level: &Weak<World>,
282 ) -> Vec<SharedEntity> {
283 let mut entities = Vec::new();
284 let Some(entity) = Self::persistent_to_entity_at_level(persistent, chunk_pos, level) else {
285 return entities;
286 };
287
288 entities.push(Arc::clone(&entity));
289 for persistent_passenger in &persistent.passengers {
290 Self::load_persistent_passenger_tree(
291 persistent_passenger,
292 chunk_pos,
293 level,
294 &entity,
295 &mut entities,
296 );
297 }
298 entities
299 }
300
301 pub(super) fn load_persistent_passenger_tree(
302 persistent: &PersistentEntity,
303 chunk_pos: ChunkPos,
304 level: &Weak<World>,
305 vehicle: &SharedEntity,
306 entities: &mut Vec<SharedEntity>,
307 ) {
308 let Some(passenger) = Self::persistent_to_entity_at_level(persistent, chunk_pos, level)
309 else {
310 return;
311 };
312
313 EntityBase::restore_passenger_relationship(vehicle, &passenger);
314 entities.push(Arc::clone(&passenger));
315 for persistent_passenger in &persistent.passengers {
316 Self::load_persistent_passenger_tree(
317 persistent_passenger,
318 chunk_pos,
319 level,
320 &passenger,
321 entities,
322 );
323 }
324 }
325
326 pub(super) fn persistent_to_entity_at_level(
328 persistent: &PersistentEntity,
329 chunk_pos: ChunkPos,
330 level: &Weak<World>,
331 ) -> Option<SharedEntity> {
332 use uuid::Uuid;
333
334 let stored_pos = DVec3::new(persistent.pos[0], persistent.pos[1], persistent.pos[2]);
336 let mut velocity = DVec3::new(
337 persistent.motion[0],
338 persistent.motion[1],
339 persistent.motion[2],
340 );
341 let rotation = (persistent.rotation[0], persistent.rotation[1]);
342 let uuid = Uuid::from_bytes(persistent.uuid);
343
344 if !stored_pos.x.is_finite() || !stored_pos.y.is_finite() || !stored_pos.z.is_finite() {
346 tracing::warn!(
347 ?uuid,
348 "Entity has non-finite position {:?}, skipping load",
349 stored_pos
350 );
351 return None;
352 }
353
354 if !rotation.0.is_finite() || !rotation.1.is_finite() {
355 tracing::warn!(
356 ?uuid,
357 "Entity has non-finite rotation {rotation:?}, skipping load"
358 );
359 return None;
360 }
361
362 let pos = clamp_loaded_entity_position(stored_pos);
363
364 let expected_chunk = ChunkPos::from_entity_pos(pos);
366 if chunk_pos != expected_chunk {
367 tracing::warn!(
368 ?uuid,
369 "Entity position {:?} doesn't match chunk {:?}, loading anyway",
370 pos,
371 chunk_pos
372 );
373 }
374
375 if velocity.x.abs() > 10.0 {
377 velocity.x = 0.0;
378 }
379 if velocity.y.abs() > 10.0 {
380 velocity.y = 0.0;
381 }
382 if velocity.z.abs() > 10.0 {
383 velocity.z = 0.0;
384 }
385
386 let entity_type = REGISTRY.entity_types.by_key(&persistent.entity_type)?;
388 let save_data = Self::save_data_from_persistent(persistent, uuid);
389
390 let nbt_bytes = if persistent.nbt_data.is_empty() {
392 &[0x00][..]
394 } else {
395 &persistent.nbt_data[..]
396 };
397
398 let Ok(nbt) = read_borrowed_compound(&mut Cursor::new(nbt_bytes)) else {
399 tracing::warn!(?uuid, "Failed to parse entity NBT, skipping");
400 return None;
401 };
402
403 ENTITIES.create_and_load(
404 EntityLoadRequest {
405 entity_type,
406 position: pos,
407 uuid,
408 velocity,
409 rotation,
410 fall_distance: persistent.fall_distance,
411 fire_freeze: EntityFireFreezeState::from_parts(
412 persistent.remaining_fire_ticks,
413 persistent.ticks_frozen,
414 persistent.is_in_powder_snow,
415 persistent.was_in_powder_snow,
416 persistent.has_visual_fire,
417 ),
418 on_ground: persistent.on_ground,
419 save_data,
420 world: Weak::clone(level),
421 },
422 &nbt,
423 )
424 }
425}