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