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