1use rustc_hash::FxHashSet;
6use steel_registry::item_stack::ItemStack;
7use steel_registry::stat::Stat;
8use steel_utils::Identifier;
9use steel_utils::types::GameType;
10
11use super::advancement::PlayerAdvancement;
12use super::advancement::progress::AdvancementProgress;
13use super::{
14 Player, PlayerRespawnConfig, abilities::Abilities, experience::Experience, food_data::FoodData,
15 player_inventory::PlayerInventory,
16};
17use crate::player::stats_counter::StatState;
18use crate::{
19 chunk_saver::{ChunkStorage, PersistentEntity},
20 entity::{Entity, EntityFireFreezeState, LivingEntity},
21 inventory::{
22 container::Container,
23 ender_chest::{ENDER_CHEST_SLOTS, PlayerEnderChestContainer},
24 },
25};
26
27pub const PLAYER_DATA_VERSION: i32 = 7;
30
31#[derive(Debug, Clone)]
36pub struct PersistentPlayerData {
37 pub pos: [f64; 3],
39
40 pub motion: [f64; 3],
42
43 pub rotation: [f32; 2],
45
46 pub on_ground: bool,
48
49 pub fall_flying: bool,
51
52 pub remaining_fire_ticks: i32,
54
55 pub ticks_frozen: i32,
57
58 pub is_in_powder_snow: bool,
60
61 pub was_in_powder_snow: bool,
63
64 pub has_visual_fire: bool,
66
67 pub health: f32,
69
70 pub game_mode: i32,
72
73 pub prev_game_mode: Option<i32>,
75
76 pub abilities: PersistentAbilities,
78
79 pub inventory: Vec<PersistentSlot>,
81
82 pub selected_slot: i32,
84
85 pub world: String,
87
88 pub food_level: i32,
90
91 pub food_saturation_level: f32,
93
94 pub food_exhaustion_level: f32,
96
97 pub food_tick_timer: i32,
99
100 pub data_version: i32,
102
103 pub experience_level: i32,
105
106 pub experience_progress: f32,
108
109 pub experience_total: i32,
111
112 pub enchantment_seed: i32,
114
115 pub score: i32,
117
118 pub seen_credits: bool,
120
121 pub root_vehicle: Option<PersistentRootVehicle>,
123
124 pub respawn_config: Option<PlayerRespawnConfig>,
126
127 pub ender_pearls: Vec<PersistentEnderPearl>,
129
130 pub ender_items: Vec<PersistentSlot>,
132
133 pub stats: Vec<PersistentStat>,
135
136 pub advancements: Vec<(Identifier, AdvancementProgress)>,
138}
139
140#[derive(Debug, Clone)]
142pub struct PersistentRootVehicle {
143 pub attach: [u8; 16],
145 pub entity: PersistentEntity,
147}
148
149#[derive(Debug, Clone)]
154pub struct PersistentEnderPearl {
155 pub world: String,
157 pub entity: PersistentEntity,
159}
160
161#[derive(Debug, Clone)]
163pub struct PersistentAbilities {
164 pub invulnerable: bool,
166 pub flying: bool,
168 pub may_fly: bool,
170 pub instabuild: bool,
172 pub may_build: bool,
174 pub flying_speed: f32,
176 pub walking_speed: f32,
178}
179
180#[derive(Debug, Clone)]
182pub struct PersistentSlot {
183 pub slot: i8,
185 pub item: ItemStack,
187}
188
189#[derive(Debug, Clone)]
191pub struct PersistentStat {
192 pub stat: Stat,
194 pub count: i32,
196}
197
198impl PersistentPlayerData {
199 #[must_use]
201 pub fn from_player(player: &Player) -> Self {
202 let pos = player.position();
203 let (yaw, pitch) = player.rotation();
204 let delta = player.velocity();
205 let on_ground = player.on_ground();
206 let fall_flying = player.is_fall_flying();
207 let fire_freeze = player.fire_freeze_state();
208 let abilities = player.abilities.lock();
209 let inventory = player.inventory.lock();
210 let food_data = player.food_data.lock();
211
212 let mut slots = Vec::new();
214 for slot in 0..PlayerInventory::CONTAINER_SIZE {
216 let item = inventory.get_item(slot);
217 if !item.is_empty() {
218 slots.push(PersistentSlot {
219 slot: slot as i8,
220 item: item.clone(),
221 });
222 }
223 }
224
225 let ender_chest_inventory = player.ender_chest_inventory.lock();
226 let mut ender_items = Vec::new();
227 for slot in 0..ENDER_CHEST_SLOTS {
228 let item = ender_chest_inventory.get_item(slot);
229 if !item.is_empty() {
230 ender_items.push(PersistentSlot {
231 slot: slot as i8,
232 item: item.clone(),
233 });
234 }
235 }
236
237 let (experience_level, experience_progress, experience_total, enchantment_seed) = {
238 let lock = player.experience.lock();
239 (
240 lock.level(),
241 lock.progress(),
242 lock.total_points(),
243 lock.enchantment_seed(),
244 )
245 };
246 let score = player.score();
247 let root_vehicle = Self::root_vehicle_from_player(player)
248 .or_else(|| player.pending_root_vehicle_for_current_world());
249 let ender_pearls = Self::ender_pearls_from_player(player);
250 let stats = Self::stats_from_player(player);
251 let advancements = player.advancements.lock().persisted_progress();
252
253 Self {
254 pos: [pos.x, pos.y, pos.z],
255 motion: [delta.x, delta.y, delta.z],
256 rotation: [yaw, pitch],
257 on_ground,
258 fall_flying,
259 remaining_fire_ticks: fire_freeze.remaining_fire_ticks(),
260 ticks_frozen: fire_freeze.ticks_frozen(),
261 is_in_powder_snow: fire_freeze.is_in_powder_snow(),
262 was_in_powder_snow: fire_freeze.was_in_powder_snow(),
263 has_visual_fire: fire_freeze.has_visual_fire(),
264 health: player.get_health(),
265 game_mode: player.game_mode() as i32,
266 prev_game_mode: player
267 .previous_game_mode()
268 .map(|game_mode| game_mode as i32),
269 abilities: PersistentAbilities {
270 invulnerable: abilities.invulnerable,
271 flying: abilities.flying,
272 may_fly: abilities.may_fly,
273 instabuild: abilities.instabuild,
274 may_build: abilities.may_build,
275 flying_speed: abilities.flying_speed,
276 walking_speed: abilities.walking_speed,
277 },
278 inventory: slots,
279 selected_slot: i32::from(inventory.get_selected_slot()),
280 world: player.get_world().key.to_string(),
281 food_level: food_data.food_level,
282 food_saturation_level: food_data.saturation_level,
283 food_exhaustion_level: food_data.exhaustion_level,
284 food_tick_timer: food_data.tick_timer,
285 data_version: PLAYER_DATA_VERSION,
286 experience_level,
287 experience_progress,
288 experience_total,
289 enchantment_seed,
290 score,
291 seen_credits: player.has_seen_credits(),
292 root_vehicle,
293 respawn_config: player.respawn_config(),
294
295 ender_pearls,
296 ender_items,
297 stats,
298 advancements,
299 }
300 }
301
302 fn ender_pearls_from_player(player: &Player) -> Vec<PersistentEnderPearl> {
304 let mut seen = FxHashSet::default();
305 let mut pearls = player
306 .ender_pearls()
307 .iter()
308 .filter_map(|pearl| {
309 let world = pearl.level()?.key.to_string();
310 let entity = ChunkStorage::entity_tree_to_persistent(pearl)?;
311 seen.insert(entity.uuid);
312 Some(PersistentEnderPearl { world, entity })
313 })
314 .collect::<Vec<_>>();
315 pearls.extend(
316 player
317 .pending_ender_pearls()
318 .into_iter()
319 .filter(|pearl| seen.insert(pearl.entity.uuid)),
320 );
321 pearls
322 }
323
324 fn stats_from_player(player: &Player) -> Vec<PersistentStat> {
326 player
327 .stats()
328 .into_iter()
329 .map(|(stat, count)| PersistentStat { stat, count })
330 .collect()
331 }
332
333 fn root_vehicle_from_player(player: &Player) -> Option<PersistentRootVehicle> {
334 let vehicle = player.vehicle()?;
335 let root_vehicle = player.root_vehicle()?;
336 if root_vehicle.id() == player.id() || !root_vehicle.has_exactly_one_player_passenger() {
337 return None;
338 }
339
340 let entity = ChunkStorage::entity_tree_to_persistent(&root_vehicle)?;
341 Some(PersistentRootVehicle {
342 attach: *vehicle.uuid().as_bytes(),
343 entity,
344 })
345 }
346}
347
348impl Player {
349 pub(crate) fn reset_domain_data_for_first_visit(&self) {
351 use glam::DVec3;
352
353 self.set_velocity(DVec3::ZERO);
354 self.set_on_ground(false);
355 self.set_fall_flying(false);
356 self.base()
357 .set_fire_freeze_state(EntityFireFreezeState::new());
358 self.sync_base_fire_freeze_entity_data();
359 self.set_health(self.get_max_health());
360 *self.abilities.lock() = Abilities::default();
361 *self.inventory.lock() = PlayerInventory::new();
362 *self.ender_chest_inventory.lock() = PlayerEnderChestContainer::new();
363 *self.food_data.lock() = FoodData::new();
364 self.stats.lock().reset();
365 *self.advancements.lock() = PlayerAdvancement::default();
366
367 let mut experience = Experience::default();
368 experience.dirty = true;
369 *self.experience.lock() = experience;
370
371 self.set_score(0);
372 self.set_seen_credits(false);
373 }
374}
375
376impl Default for PersistentAbilities {
377 fn default() -> Self {
378 Self {
379 invulnerable: false,
380 flying: false,
381 may_fly: false,
382 instabuild: false,
383 may_build: true,
384 flying_speed: 0.05,
385 walking_speed: 0.1,
386 }
387 }
388}
389
390impl From<&Abilities> for PersistentAbilities {
391 fn from(abilities: &Abilities) -> Self {
392 Self {
393 invulnerable: abilities.invulnerable,
394 flying: abilities.flying,
395 may_fly: abilities.may_fly,
396 instabuild: abilities.instabuild,
397 may_build: abilities.may_build,
398 flying_speed: abilities.flying_speed,
399 walking_speed: abilities.walking_speed,
400 }
401 }
402}
403
404impl From<PersistentAbilities> for Abilities {
405 fn from(persistent: PersistentAbilities) -> Self {
406 Self {
407 invulnerable: persistent.invulnerable,
408 flying: persistent.flying,
409 may_fly: persistent.may_fly,
410 instabuild: persistent.instabuild,
411 may_build: persistent.may_build,
412 flying_speed: persistent.flying_speed,
413 walking_speed: persistent.walking_speed,
414 }
415 }
416}
417
418impl PersistentPlayerData {
419 pub fn apply_to_player(&self, player: &Player) {
423 self.apply_to_player_inner(player, true);
424 }
425
426 pub fn apply_to_player_without_location(&self, player: &Player) {
431 self.apply_to_player_inner(player, false);
432 }
433
434 fn apply_to_player_inner(&self, player: &Player, restore_location: bool) {
435 use glam::DVec3;
436
437 if restore_location {
438 player
440 .base()
441 .set_position_local(DVec3::new(self.pos[0], self.pos[1], self.pos[2]));
442
443 player.set_rotation((self.rotation[0], self.rotation[1]));
445
446 player.set_velocity(DVec3::new(self.motion[0], self.motion[1], self.motion[2]));
448
449 player.set_fall_flying(self.fall_flying);
451 player.set_on_ground(self.on_ground);
452 }
453
454 player
455 .base()
456 .set_fire_freeze_state(EntityFireFreezeState::from_parts(
457 self.remaining_fire_ticks,
458 self.ticks_frozen,
459 self.is_in_powder_snow,
460 self.was_in_powder_snow,
461 self.has_visual_fire,
462 ));
463 player.sync_base_fire_freeze_entity_data();
464 player.set_respawn_position(self.respawn_config.clone(), false);
465
466 player.set_health(self.health);
468
469 player.restore_game_modes(
471 self.game_mode.into(),
472 self.prev_game_mode.map(GameType::from),
473 );
474
475 *player.abilities.lock() = self.abilities.clone().into();
477
478 {
480 let mut inventory = player.inventory.lock();
481 for slot in 0..PlayerInventory::CONTAINER_SIZE {
483 inventory.set_item(slot, ItemStack::empty());
484 }
485 for slot_data in &self.inventory {
487 let slot_index = slot_data.slot as usize;
488 if slot_index < PlayerInventory::CONTAINER_SIZE {
489 inventory.set_item(slot_index, slot_data.item.clone());
490 }
491 }
492 let selected = self.selected_slot.clamp(0, 8) as u8;
494 inventory.set_selected_slot(selected);
495 }
496
497 {
498 let mut ender_chest_inventory = player.ender_chest_inventory.lock();
499 for slot in 0..ENDER_CHEST_SLOTS {
500 ender_chest_inventory.set_item(slot, ItemStack::empty());
501 }
502 for slot_data in &self.ender_items {
503 let slot_index = slot_data.slot as usize;
504 if slot_index < ENDER_CHEST_SLOTS {
505 ender_chest_inventory.set_item(slot_index, slot_data.item.clone());
506 }
507 }
508 }
509
510 {
512 let mut food = player.food_data.lock();
513 food.food_level = self.food_level;
514 food.saturation_level = self.food_saturation_level;
515 food.exhaustion_level = self.food_exhaustion_level;
516 food.tick_timer = self.food_tick_timer;
517 }
518
519 {
520 let mut experience = player.experience.lock();
521 *experience = Experience::from_parts(
522 self.experience_level,
523 self.experience_progress,
524 self.experience_total,
525 )
526 .with_loaded_enchantment_seed(self.enchantment_seed, rand::random);
527 }
528 player.set_score(self.score);
529 player.set_seen_credits(self.seen_credits);
530
531 {
533 let mut stats = player.stats.lock();
534
535 stats.reset();
540
541 for PersistentStat { stat, count } in &self.stats {
542 stats.stats.insert(*stat, (*count, StatState::Dirty));
543 }
544 }
545
546 player
547 .advancements
548 .lock()
549 .apply_persisted_progress(self.advancements.iter().cloned());
550 }
551}