1use rustc_hash::FxHashSet;
6use steel_registry::item_stack::ItemStack;
7use steel_utils::types::GameType;
8
9use crate::{
10 chunk_saver::{ChunkStorage, PersistentEntity},
11 entity::{Entity, EntityFireFreezeState, LivingEntity},
12 inventory::container::Container,
13};
14
15use super::{
16 Player, abilities::Abilities, experience::Experience, food_data::FoodData,
17 player_inventory::PlayerInventory,
18};
19
20pub const PLAYER_DATA_VERSION: i32 = 5;
23
24#[derive(Debug, Clone)]
29pub struct PersistentPlayerData {
30 pub pos: [f64; 3],
32
33 pub motion: [f64; 3],
35
36 pub rotation: [f32; 2],
38
39 pub on_ground: bool,
41
42 pub fall_flying: bool,
44
45 pub remaining_fire_ticks: i32,
47
48 pub ticks_frozen: i32,
50
51 pub is_in_powder_snow: bool,
53
54 pub was_in_powder_snow: bool,
56
57 pub has_visual_fire: bool,
59
60 pub health: f32,
62
63 pub game_mode: i32,
65
66 pub prev_game_mode: Option<i32>,
68
69 pub abilities: PersistentAbilities,
71
72 pub inventory: Vec<PersistentSlot>,
74
75 pub selected_slot: i32,
77
78 pub world: String,
80
81 pub food_level: i32,
83
84 pub food_saturation_level: f32,
86
87 pub food_exhaustion_level: f32,
89
90 pub food_tick_timer: i32,
92
93 pub data_version: i32,
95
96 pub experience_level: i32,
98
99 pub experience_progress: f32,
101
102 pub experience_total: i32,
104
105 pub score: i32,
107
108 pub seen_credits: bool,
110
111 pub root_vehicle: Option<PersistentRootVehicle>,
113
114 pub ender_pearls: Vec<PersistentEnderPearl>,
116}
117
118#[derive(Debug, Clone)]
120pub struct PersistentRootVehicle {
121 pub attach: [u8; 16],
123 pub entity: PersistentEntity,
125}
126
127#[derive(Debug, Clone)]
132pub struct PersistentEnderPearl {
133 pub world: String,
135 pub entity: PersistentEntity,
137}
138
139#[derive(Debug, Clone)]
141pub struct PersistentAbilities {
142 pub invulnerable: bool,
144 pub flying: bool,
146 pub may_fly: bool,
148 pub instabuild: bool,
150 pub may_build: bool,
152 pub flying_speed: f32,
154 pub walking_speed: f32,
156}
157
158#[derive(Debug, Clone)]
160pub struct PersistentSlot {
161 pub slot: i8,
163 pub item: ItemStack,
165}
166
167impl PersistentPlayerData {
168 #[must_use]
170 pub fn from_player(player: &Player) -> Self {
171 let pos = player.position();
172 let (yaw, pitch) = player.rotation();
173 let delta = player.velocity();
174 let on_ground = player.on_ground();
175 let fall_flying = player.is_fall_flying();
176 let fire_freeze = player.fire_freeze_state();
177 let abilities = player.abilities.lock();
178 let inventory = player.inventory.lock();
179 let food_data = player.food_data.lock();
180
181 let mut slots = Vec::new();
183 for slot in 0..PlayerInventory::CONTAINER_SIZE {
185 let item = inventory.get_item(slot);
186 if !item.is_empty() {
187 slots.push(PersistentSlot {
188 slot: slot as i8,
189 item: item.clone(),
190 });
191 }
192 }
193
194 let (experience_level, experience_progress, experience_total) = {
195 let lock = player.experience.lock();
196 (lock.level(), lock.progress(), lock.total_points())
197 };
198 let score = player.score();
199 let root_vehicle = Self::root_vehicle_from_player(player)
200 .or_else(|| player.pending_root_vehicle_for_current_world());
201 let ender_pearls = Self::ender_pearls_from_player(player);
202
203 Self {
204 pos: [pos.x, pos.y, pos.z],
205 motion: [delta.x, delta.y, delta.z],
206 rotation: [yaw, pitch],
207 on_ground,
208 fall_flying,
209 remaining_fire_ticks: fire_freeze.remaining_fire_ticks(),
210 ticks_frozen: fire_freeze.ticks_frozen(),
211 is_in_powder_snow: fire_freeze.is_in_powder_snow(),
212 was_in_powder_snow: fire_freeze.was_in_powder_snow(),
213 has_visual_fire: fire_freeze.has_visual_fire(),
214 health: player.get_health(),
215 game_mode: player.game_mode() as i32,
216 prev_game_mode: player
217 .previous_game_mode()
218 .map(|game_mode| game_mode as i32),
219 abilities: PersistentAbilities {
220 invulnerable: abilities.invulnerable,
221 flying: abilities.flying,
222 may_fly: abilities.may_fly,
223 instabuild: abilities.instabuild,
224 may_build: abilities.may_build,
225 flying_speed: abilities.flying_speed,
226 walking_speed: abilities.walking_speed,
227 },
228 inventory: slots,
229 selected_slot: i32::from(inventory.get_selected_slot()),
230 world: player.get_world().key.to_string(),
231 food_level: food_data.food_level,
232 food_saturation_level: food_data.saturation_level,
233 food_exhaustion_level: food_data.exhaustion_level,
234 food_tick_timer: food_data.tick_timer,
235 data_version: PLAYER_DATA_VERSION,
236 experience_level,
237 experience_progress,
238 experience_total,
239 score,
240 seen_credits: player.has_seen_credits(),
241 root_vehicle,
242 ender_pearls,
243 }
244 }
245
246 fn ender_pearls_from_player(player: &Player) -> Vec<PersistentEnderPearl> {
248 let mut seen = FxHashSet::default();
249 let mut pearls = player
250 .ender_pearls()
251 .iter()
252 .filter_map(|pearl| {
253 let world = pearl.level()?.key.to_string();
254 let entity = ChunkStorage::entity_tree_to_persistent(pearl)?;
255 seen.insert(entity.uuid);
256 Some(PersistentEnderPearl { world, entity })
257 })
258 .collect::<Vec<_>>();
259 pearls.extend(
260 player
261 .pending_ender_pearls()
262 .into_iter()
263 .filter(|pearl| seen.insert(pearl.entity.uuid)),
264 );
265 pearls
266 }
267
268 fn root_vehicle_from_player(player: &Player) -> Option<PersistentRootVehicle> {
269 let vehicle = player.vehicle()?;
270 let root_vehicle = player.root_vehicle()?;
271 if root_vehicle.id() == player.id() || !root_vehicle.has_exactly_one_player_passenger() {
272 return None;
273 }
274
275 let entity = ChunkStorage::entity_tree_to_persistent(&root_vehicle)?;
276 Some(PersistentRootVehicle {
277 attach: *vehicle.uuid().as_bytes(),
278 entity,
279 })
280 }
281}
282
283impl Player {
284 pub(crate) fn reset_domain_data_for_first_visit(&self) {
286 use glam::DVec3;
287
288 self.set_velocity(DVec3::ZERO);
289 self.set_on_ground(false);
290 self.set_fall_flying(false);
291 self.base()
292 .set_fire_freeze_state(EntityFireFreezeState::new());
293 self.sync_base_fire_freeze_entity_data();
294 self.set_health(self.get_max_health());
295 *self.abilities.lock() = Abilities::default();
296 *self.inventory.lock() = PlayerInventory::new();
297 *self.food_data.lock() = FoodData::new();
298
299 let mut experience = Experience::default();
300 experience.dirty = true;
301 *self.experience.lock() = experience;
302
303 self.set_score(0);
304 self.set_seen_credits(false);
305 }
306}
307
308impl Default for PersistentAbilities {
309 fn default() -> Self {
310 Self {
311 invulnerable: false,
312 flying: false,
313 may_fly: false,
314 instabuild: false,
315 may_build: true,
316 flying_speed: 0.05,
317 walking_speed: 0.1,
318 }
319 }
320}
321
322impl From<&Abilities> for PersistentAbilities {
323 fn from(abilities: &Abilities) -> Self {
324 Self {
325 invulnerable: abilities.invulnerable,
326 flying: abilities.flying,
327 may_fly: abilities.may_fly,
328 instabuild: abilities.instabuild,
329 may_build: abilities.may_build,
330 flying_speed: abilities.flying_speed,
331 walking_speed: abilities.walking_speed,
332 }
333 }
334}
335
336impl From<PersistentAbilities> for Abilities {
337 fn from(persistent: PersistentAbilities) -> Self {
338 Self {
339 invulnerable: persistent.invulnerable,
340 flying: persistent.flying,
341 may_fly: persistent.may_fly,
342 instabuild: persistent.instabuild,
343 may_build: persistent.may_build,
344 flying_speed: persistent.flying_speed,
345 walking_speed: persistent.walking_speed,
346 }
347 }
348}
349
350impl PersistentPlayerData {
351 pub fn apply_to_player(&self, player: &Player) {
355 self.apply_to_player_inner(player, true);
356 }
357
358 pub fn apply_to_player_without_location(&self, player: &Player) {
363 self.apply_to_player_inner(player, false);
364 }
365
366 fn apply_to_player_inner(&self, player: &Player, restore_location: bool) {
367 use glam::DVec3;
368
369 if restore_location {
370 player
372 .base()
373 .set_position_local(DVec3::new(self.pos[0], self.pos[1], self.pos[2]));
374
375 player.set_rotation((self.rotation[0], self.rotation[1]));
377
378 player.set_velocity(DVec3::new(self.motion[0], self.motion[1], self.motion[2]));
380
381 player.set_fall_flying(self.fall_flying);
383 player.set_on_ground(self.on_ground);
384 }
385
386 player
387 .base()
388 .set_fire_freeze_state(EntityFireFreezeState::from_parts(
389 self.remaining_fire_ticks,
390 self.ticks_frozen,
391 self.is_in_powder_snow,
392 self.was_in_powder_snow,
393 self.has_visual_fire,
394 ));
395 player.sync_base_fire_freeze_entity_data();
396
397 player.set_health(self.health);
399
400 player.restore_game_modes(
402 self.game_mode.into(),
403 self.prev_game_mode.map(GameType::from),
404 );
405
406 *player.abilities.lock() = self.abilities.clone().into();
408
409 {
411 let mut inventory = player.inventory.lock();
412 for slot in 0..PlayerInventory::CONTAINER_SIZE {
414 inventory.set_item(slot, ItemStack::empty());
415 }
416 for slot_data in &self.inventory {
418 let slot_index = slot_data.slot as usize;
419 if slot_index < PlayerInventory::CONTAINER_SIZE {
420 inventory.set_item(slot_index, slot_data.item.clone());
421 }
422 }
423 let selected = self.selected_slot.clamp(0, 8) as u8;
425 inventory.set_selected_slot(selected);
426 }
427
428 {
430 let mut food = player.food_data.lock();
431 food.food_level = self.food_level;
432 food.saturation_level = self.food_saturation_level;
433 food.exhaustion_level = self.food_exhaustion_level;
434 food.tick_timer = self.food_tick_timer;
435 }
436
437 {
438 let mut experience = player.experience.lock();
439 *experience = Experience::from_parts(
440 self.experience_level,
441 self.experience_progress,
442 self.experience_total,
443 );
444 }
445 player.set_score(self.score);
446 player.set_seen_credits(self.seen_credits);
447 }
448}