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