1mod abilities;
3pub mod chat;
4pub mod chunk_sender;
5pub mod connection;
7mod container_counter;
8mod entity_state;
9pub mod experience;
11pub mod food_data;
12pub mod game_mode;
14mod health_sync;
15mod item_cooldowns;
16mod lifecycle;
17pub mod movement;
18mod permissions;
19pub mod player_data;
20pub mod player_data_storage;
21pub mod player_inventory;
22mod profile;
23mod session;
24mod sleep;
25mod sleep_state;
26mod spam_throttler;
27pub mod stats_counter;
28mod tick_state;
29mod title;
30
31pub use abilities::{Abilities, DEFAULT_FLYING_SPEED};
32use chat::ChatState;
33pub use chat::{LastSeen, LastSeenMessagesValidator, MessageCache};
34use connection::NetworkConnection as _;
35pub use connection::{ClientInformation, PlayerConnection};
36use container_counter::ContainerCounter;
37use food_data::{FoodData, food_constants};
38use game_mode::{BlockBreakingManager, PlayerGameModeState};
39use glam::DVec3;
40use health_sync::HealthSyncState;
41use item_cooldowns::ItemCooldowns;
42use lifecycle::PlayerLifecycleState;
43pub use lifecycle::PlayerRespawnConfig;
44pub(crate) use lifecycle::ResetReason;
45pub use movement::PlayerInput;
46use movement::{MovementState, TeleportState};
47use permissions::PlayerPermissionState;
48pub(crate) use profile::{GAME_PROFILE_CACHE_LIMIT, KnownPlayerNameLookup, lookup_online_profile};
49pub use profile::{
50 GameProfile, GameProfileAction, KnownPlayer, KnownPlayers, ProfileLookupError,
51 is_valid_player_name, offline_uuid,
52};
53pub use session::PlayerSession;
54pub(crate) use session::PlayerSessionId;
55use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
56use sleep_state::PlayerSleepState;
57use std::ptr;
58use std::sync::atomic::Ordering;
59use std::sync::{Arc, Weak};
60use std::time::{Duration, Instant};
61use steel_protocol::packets::game::{
62 CEntityEvent, CPlayerCombatKill, CPlayerLookAt, CRespawn, CSetDefaultSpawnPosition, CSetHealth,
63 CSetHeldSlot, CSetPassengers, ClientCommandAction, LookAtAnchor, RelativeMovement, SoundSource,
64};
65use steel_protocol::packets::game::{CLevelEvent, CSetEntityData, CSetExperience};
66use steel_registry::blocks::block_state_ext::BlockStateExt as _;
67use steel_registry::entity_data::{EntityPose, HumanoidArm, ParticleList};
68use steel_registry::entity_type::{EntityDimensions, EntityTypeRef};
69use steel_registry::game_rules::GameRuleRef;
70use steel_registry::sound_event::SoundEventRef;
71use steel_registry::vanilla_block_tags::BlockTag;
72use steel_registry::vanilla_entity_data::PlayerEntityData;
73use steel_registry::vanilla_game_rules::{
74 DROWNING_DAMAGE, FALL_DAMAGE, FIRE_DAMAGE, FREEZE_DAMAGE, IMMEDIATE_RESPAWN, KEEP_INVENTORY,
75 SHOW_DEATH_MESSAGES,
76};
77use steel_registry::{
78 level_events, sound_events, vanilla_attributes, vanilla_custom_stats, vanilla_damage_type_tags,
79 vanilla_entities, vanilla_game_events,
80};
81use steel_utils::{entity_events::EntityStatus, locks::Shared, translations};
82use tick_state::PlayerTickState;
83use uuid::Uuid;
84
85use arc_swap::ArcSwap;
86use steel_utils::locks::SyncMutex;
87use steel_utils::types::{Difficulty, GameType, InteractionHand};
88use text_components::resolving::TextResolutor;
89use text_components::translation::TranslatedMessage;
90use text_components::{
91 Modifier as _, TextComponent,
92 interactivity::{ClickEvent, HoverEvent},
93};
94use text_components::{content::Resolvable, custom::CustomData};
95
96use crate::behavior::{
97 BlockStateBehaviorExt as _, ITEM_BEHAVIORS, InteractionResult, ItemBehavior,
98 apply_use_remainder,
99};
100use crate::chunk::chunk_request::{ChunkRequestHandle, ChunkRequestState};
101use crate::config::RuntimeConfig;
102use crate::enchantment_helper;
103use crate::entity::damage::DamageSource;
104use crate::entity::entities::ExperienceOrbEntity;
105use crate::entity::{
106 ActiveItemUseState, DEATH_DURATION, Entity, EntityAnchor, EntityBase, EntityEventSource,
107 EntityMoveError, EntityMovementEmission, EntitySyncedData, LivingEntity, LivingEntityBase,
108 LivingEntitySyncedData, MobEffectSyncChange, MobEffectSyncPacket, RemovalReason, SharedEntity,
109 apply_entity_look_at, get_kill_credit, start_riding_entities,
110};
111use crate::fluid::get_fluid_state;
112use crate::inventory::equipment::{EntityEquipment, EquipmentSlot};
113use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
114use crate::inventory::menu::Menu;
115use crate::inventory::menu::kinds::inventory_menu;
116use crate::level_data::RespawnData;
117use crate::permission::{
118 PermissionContext, PermissionExpr, PermissionMetadataSet, PermissionMetadataValue,
119 PermissionSet, PermissionState,
120};
121use crate::physics::MoveResult;
122use crate::player::experience::Experience;
123use crate::player::player_data::{PersistentEnderPearl, PersistentRootVehicle};
124use crate::player::player_inventory::{
125 MenuItemDisposition, MenuRemovalStatus, PlayerInventory, PlayerInventorySyncState,
126};
127use crate::server::{
128 PlayerPacketTransition, Server,
129 jobs::{JobPoll, ServerJob, ServerJobContext},
130};
131use crate::world::player_spawn_finder::{PlayerSpawnSearch, PlayerSpawnSearchPoll};
132use steel_registry::vanilla_damage_types;
133
134use steel_protocol::packets::{
135 common::SCustomPayload,
136 game::{CContainerClose, CGameEvent, CSystemChat, GameEventType},
137};
138use steel_registry::RegistryEntry;
139use steel_registry::item_stack::ItemStack;
140use steel_registry::items::ItemRef;
141use steel_registry::stat::vanilla_stat_types;
142use steel_utils::{
143 BlockPos, BlockStateId, ChunkPos, DowncastType, DowncastTypeKey, Identifier, UuidExt as _,
144};
145
146use crate::inventory::container::Container;
147
148const RESPAWN_SEARCH_READY_CANDIDATE_BUDGET: usize = 8;
149const HAT_MODEL_PART_MASK: i8 = 0b0100_0000;
150
151const DROP_SPAM_THROTTLER_INCREMENT_STEP: i32 = 20;
152const DROP_SPAM_THROTTLER_THRESHOLD: i32 = 1480;
153
154use crate::chunk::player_chunk_view::PlayerChunkView;
155use crate::entity::entities::objects::projectiles::FishingHookEntity;
156use crate::inventory::ender_chest::{PlayerEnderChestContainer, SyncPlayerEnderChest};
157use crate::player::chunk_sender::ChunkSender;
158use crate::player::stats_counter::StatsCounter;
159use crate::portal::{
160 PortalTicketTarget, TeleportPostAction, TeleportPostTransition, TeleportTransition,
161};
162use crate::world::World;
163
164pub struct Player {
166 pub gameprofile: GameProfile,
168 pub connection: Arc<PlayerConnection>,
170 pub(crate) session: Arc<PlayerSession>,
172
173 pub world: ArcSwap<World>,
175
176 pub(crate) server: Weak<Server>,
178 pub(crate) config: Arc<RuntimeConfig>,
180
181 base: EntityBase,
183
184 lifecycle: SyncMutex<PlayerLifecycleState>,
186
187 pub(crate) movement: SyncMutex<MovementState>,
189
190 entity_data: SyncMutex<PlayerEntityData>,
192
193 pub last_chunk_pos: SyncMutex<ChunkPos>,
195 pub last_tracking_view: SyncMutex<Option<PlayerChunkView>>,
197 client_information: SyncMutex<ClientInformation>,
200
201 game_modes: SyncMutex<PlayerGameModeState>,
203
204 pub inventory: Shared<PlayerInventory>,
206
207 inventory_sync: SyncMutex<PlayerInventorySyncState>,
209
210 pub ender_chest_inventory: SyncPlayerEnderChest,
212
213 last_item_in_main_hand: SyncMutex<ItemStack>,
215
216 inventory_menu: SyncMutex<Menu>,
218
219 open_menu: SyncMutex<player_inventory::OpenMenuState>,
222
223 container_counter: SyncMutex<ContainerCounter>,
225
226 teleport_state: SyncMutex<TeleportState>,
228 item_cooldowns: SyncMutex<ItemCooldowns>,
230
231 tick_state: SyncMutex<PlayerTickState>,
233 sleep_state: SyncMutex<PlayerSleepState>,
235 respawn_config: SyncMutex<Option<PlayerRespawnConfig>>,
237
238 pub abilities: SyncMutex<Abilities>,
240
241 pub block_breaking: SyncMutex<BlockBreakingManager>,
243
244 living_base: LivingEntityBase,
247
248 pub food_data: SyncMutex<FoodData>,
250
251 health_sync: SyncMutex<HealthSyncState>,
253
254 pub experience: SyncMutex<Experience>,
256
257 permissions: SyncMutex<PlayerPermissionState>,
259
260 seen_credits: SyncMutex<bool>,
262
263 won_game: SyncMutex<bool>,
265
266 pub chunk_send_epoch: SyncMutex<u32>,
269
270 residence: SyncMutex<PlayerResidenceState>,
272 ender_pearls: SyncMutex<Vec<Weak<dyn Entity>>>,
275
276 pub(crate) fishing: SyncMutex<Option<Weak<FishingHookEntity>>>,
278
279 stats: SyncMutex<StatsCounter>,
281
282 last_action_time: SyncMutex<Instant>,
284}
285
286unsafe impl DowncastType for Player {
288 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/player");
289}
290
291#[derive(Clone)]
292struct PendingRootVehicleRestore {
293 world: Identifier,
294 root_vehicle: PersistentRootVehicle,
295}
296
297#[derive(Clone, Copy, Debug, Eq, PartialEq)]
299pub(crate) struct DomainResidenceToken(u64);
300
301#[derive(Clone)]
302struct PlayerResidenceState {
303 token: DomainResidenceToken,
304 pending_root_vehicle: Option<PendingRootVehicleRestore>,
305 pending_ender_pearls: Vec<PersistentEnderPearl>,
306}
307
308impl PlayerResidenceState {
309 const fn new() -> Self {
310 Self {
311 token: DomainResidenceToken(1),
312 pending_root_vehicle: None,
313 pending_ender_pearls: Vec::new(),
314 }
315 }
316
317 fn advance(&mut self) -> DomainResidenceToken {
318 let Some(next_token) = self.token.0.checked_add(1) else {
319 panic!("domain residence token space exhausted");
320 };
321 self.token = DomainResidenceToken(next_token);
322 self.pending_root_vehicle = None;
323 self.pending_ender_pearls.clear();
324 self.token
325 }
326}
327
328impl Player {
329 const USING_ITEM_FLAG: i8 = 1;
330 const OFF_HAND_ACTIVE_ITEM_FLAG: i8 = 1 << 1;
331
332 pub(crate) fn chunk_sender(&self) -> &SyncMutex<ChunkSender> {
334 &self.session.chunk_sender
335 }
336
337 pub(crate) fn chat(&self) -> &SyncMutex<ChatState> {
339 &self.session.chat
340 }
341
342 #[must_use]
344 pub fn active_item_use_hand(&self) -> Option<InteractionHand> {
345 self.living_base
346 .active_item_use()
347 .map(|active| active.hand())
348 }
349
350 pub fn start_using_item(&self, hand: InteractionHand) {
352 let item = {
353 let inventory = self.inventory.lock();
354 let item = inventory.get_item_in_hand(hand);
355 item.clone()
356 };
357 let duration = ITEM_BEHAVIORS
358 .get_behavior(item.item())
359 .get_use_duration(&item, self);
360 if self.living_base.start_using_item(hand, &item, duration) {
361 let mut entity_data = self.entity_data.lock();
362 let flags = entity_data.living_entity().living_entity_flags.get();
363 let mut flags = *flags | Self::USING_ITEM_FLAG;
364 if hand == InteractionHand::OffHand {
365 flags |= Self::OFF_HAND_ACTIVE_ITEM_FLAG;
366 } else {
367 flags &= !Self::OFF_HAND_ACTIVE_ITEM_FLAG;
368 }
369 entity_data
370 .living_entity_mut()
371 .living_entity_flags
372 .set(flags);
373 }
374 }
375
376 fn stop_using_item(&self) {
377 self.living_base.stop_using_item();
378 let mut entity_data = self.entity_data.lock();
379 let flags = *entity_data.living_entity().living_entity_flags.get();
380 entity_data
381 .living_entity_mut()
382 .living_entity_flags
383 .set(flags & !Self::USING_ITEM_FLAG);
384 }
385
386 pub fn release_using_item(&self) {
388 let Some(active) = self.living_base.active_item_use() else {
389 return;
390 };
391 let behavior = ITEM_BEHAVIORS.get_behavior(active.item());
392 self.release_using_item_with_behavior(behavior, active);
393 }
394
395 fn release_using_item_with_behavior(
396 &self,
397 behavior: &dyn ItemBehavior,
398 active: ActiveItemUseState,
399 ) {
400 let hand = active.hand();
401 let item_matches = {
402 let inventory = self.inventory.lock();
403 inventory.get_item_in_hand(hand).item() == active.item()
404 };
405 if !item_matches {
406 self.stop_using_item();
407 return;
408 }
409 let mut item = {
413 let inventory = self.inventory.lock();
414 let current = inventory.get_item_in_hand(hand);
415 current.clone()
416 };
417 let stack_before_using = item.clone();
418 let world = self.get_world();
419 let apply_side_effects =
420 behavior.release_using(&mut item, &world, self, active.remaining_ticks());
421 let use_on_release = behavior.use_on_release(&item);
422 if apply_side_effects {
423 item = apply_use_remainder(&stack_before_using, item, self);
424 self.apply_item_use_cooldown(&stack_before_using);
425 }
426 self.inventory.lock().set_item_in_hand(hand, item);
427 if use_on_release && let Some(active) = self.living_base.active_item_use() {
429 self.tick_active_item_use_with_behavior(behavior, active);
430 }
431 self.stop_using_item();
432 }
433
434 fn tick_active_item_use(&self) {
435 let Some(active) = self.living_base.active_item_use() else {
436 return;
437 };
438 let behavior = ITEM_BEHAVIORS.get_behavior(active.item());
439 self.tick_active_item_use_with_behavior(behavior, active);
440 }
441
442 fn tick_active_item_use_with_behavior(
443 &self,
444 behavior: &dyn ItemBehavior,
445 active: ActiveItemUseState,
446 ) {
447 let hand = active.hand();
448 let item_matches = {
449 let inventory = self.inventory.lock();
450 inventory.get_item_in_hand(hand).item() == active.item()
451 };
452 if !item_matches {
453 self.stop_using_item();
454 return;
455 }
456 let mut item = {
457 let inventory = self.inventory.lock();
458 let current = inventory.get_item_in_hand(hand);
459 current.clone()
460 };
461 let world = self.get_world();
462 behavior.on_use_tick(&world, self, &mut item, active.remaining_ticks());
463
464 if self.active_item_use_hand() != Some(hand) {
465 self.inventory.lock().set_item_in_hand(hand, item);
466 return;
467 }
468 let Some(active) = self.living_base.decrement_active_item_use() else {
469 self.inventory.lock().set_item_in_hand(hand, item);
470 return;
471 };
472 let use_on_release = behavior.use_on_release(&item);
473 if active.remaining_ticks() == 0 && !use_on_release && !item.is_empty() {
474 let stack_before_finish = item.clone();
475 item = behavior.finish_using(&mut item, &world, self);
476 self.apply_item_use_cooldown(&stack_before_finish);
477 self.stop_using_item();
478 }
479
480 self.inventory.lock().set_item_in_hand(hand, item);
481 }
482
483 #[must_use]
485 pub fn main_arm(&self) -> HumanoidArm {
486 self.client_information.lock().main_hand
487 }
488
489 #[must_use]
490 pub(crate) fn shows_hat(&self) -> bool {
491 let model_customization = *self
492 .entity_data
493 .lock()
494 .avatar()
495 .player_mode_customization
496 .get();
497 model_customization & HAT_MODEL_PART_MASK != 0
498 }
499
500 fn apply_client_information_to_entity_data(
501 data: &mut PlayerEntityData,
502 client_information: &ClientInformation,
503 ) {
504 let avatar = data.avatar_mut();
505 avatar.player_main_hand.set(client_information.main_hand);
506 avatar
507 .player_mode_customization
508 .set(client_information.model_customization.cast_signed());
509 }
510
511 pub fn get_ray_endpoints(&self) -> (DVec3, DVec3) {
513 let pos = self.position();
514 let start_pos = DVec3::new(pos.x, self.get_eye_y(), pos.z);
515 let block_interaction_range = self.block_interaction_range();
516 let direction = self.look_angle() * block_interaction_range;
517
518 let end_pos = start_pos + direction;
519 (start_pos, end_pos)
520 }
521
522 #[must_use]
524 pub fn game_mode(&self) -> GameType {
525 self.game_modes.lock().current()
526 }
527
528 #[must_use]
530 pub fn previous_game_mode(&self) -> Option<GameType> {
531 self.game_modes.lock().previous()
532 }
533
534 pub(crate) fn restore_game_modes(&self, current: GameType, previous: Option<GameType>) {
536 self.game_modes.lock().set_pair(current, previous);
537 }
538
539 fn change_game_mode_state(&self, game_mode: GameType) -> bool {
541 self.game_modes.lock().change_current(game_mode)
542 }
543
544 #[expect(
546 clippy::too_many_arguments,
547 reason = "player construction requires explicit connection, session, world, and identity owners"
548 )]
549 pub fn new(
550 gameprofile: GameProfile,
551 connection: Arc<PlayerConnection>,
552 session: Arc<PlayerSession>,
553 world: Arc<World>,
554 server: Weak<Server>,
555 config: Arc<RuntimeConfig>,
556 entity_id: i32,
557 client_information: ClientInformation,
558 ) -> Self {
559 let inventory = Arc::new(SyncMutex::new(PlayerInventory::new()));
561 let ender_chest_inventory = Arc::new(SyncMutex::new(PlayerEnderChestContainer::new()));
562
563 let pos = DVec3::new(0.0, 0.0, 0.0);
564
565 let equipment = inventory.clone();
566 let living_base = LivingEntityBase::with_equipment(&vanilla_entities::PLAYER, equipment);
567 let player_uuid = gameprofile.id;
568 let world_ref = Arc::downgrade(&world);
569 Self {
570 gameprofile,
571 connection,
572 session,
573
574 world: ArcSwap::new(world),
575 server,
576 config,
577 base: EntityBase::with_uuid(
578 entity_id,
579 player_uuid,
580 pos,
581 Self::dimensions_for_pose(EntityPose::Standing),
582 world_ref,
583 ),
584 lifecycle: SyncMutex::new(PlayerLifecycleState::default()),
585 movement: SyncMutex::new(MovementState::new()),
586 entity_data: SyncMutex::new({
587 let mut data = PlayerEntityData::new();
588 living_base.initialize_synced_data(&mut data);
589 Self::apply_client_information_to_entity_data(&mut data, &client_information);
590 data
591 }),
592 last_chunk_pos: SyncMutex::new(ChunkPos::new(0, 0)),
593 last_tracking_view: SyncMutex::new(None),
594 client_information: SyncMutex::new(client_information),
595 game_modes: SyncMutex::new(PlayerGameModeState::new(GameType::Survival)),
596 inventory: inventory.clone(),
597 inventory_sync: SyncMutex::new(PlayerInventorySyncState::new()),
598 ender_chest_inventory,
599 last_item_in_main_hand: SyncMutex::new(ItemStack::empty()),
600 inventory_menu: SyncMutex::new(inventory_menu(inventory)),
601 open_menu: SyncMutex::new(player_inventory::OpenMenuState::new()),
602 container_counter: SyncMutex::new(ContainerCounter::new()),
603 teleport_state: SyncMutex::new(TeleportState::new()),
604 item_cooldowns: SyncMutex::new(ItemCooldowns::default()),
605 tick_state: SyncMutex::new(PlayerTickState::new()),
606 sleep_state: SyncMutex::new(PlayerSleepState::new()),
607 respawn_config: SyncMutex::new(None),
608 abilities: SyncMutex::new(Abilities::default()),
609 block_breaking: SyncMutex::new(BlockBreakingManager::new()),
610 living_base,
611 food_data: SyncMutex::new(FoodData::new()),
612 health_sync: SyncMutex::new(HealthSyncState::new()),
613 experience: SyncMutex::new(Experience::default()),
614 permissions: SyncMutex::new(PlayerPermissionState::default()),
615 seen_credits: SyncMutex::new(false),
616 won_game: SyncMutex::new(false),
617 chunk_send_epoch: SyncMutex::new(0),
618 residence: SyncMutex::new(PlayerResidenceState::new()),
619 ender_pearls: SyncMutex::new(Vec::new()),
620 fishing: SyncMutex::new(None),
621 stats: SyncMutex::new(StatsCounter::new()),
622 last_action_time: SyncMutex::new(Instant::now()),
623 }
624 }
625
626 pub fn fishing_hook(&self) -> Option<Arc<FishingHookEntity>> {
628 let mut fishing = self.fishing.lock();
629 let hook = fishing.as_ref().and_then(Weak::upgrade);
630 if hook.is_none() {
631 *fishing = None;
632 }
633 hook
634 }
635
636 pub fn set_fishing_hook(&self, hook: &Arc<FishingHookEntity>) {
638 *self.fishing.lock() = Some(Arc::downgrade(hook));
639 }
640
641 pub fn clear_fishing_hook(&self, hook: &FishingHookEntity) {
643 let mut fishing = self.fishing.lock();
644 if fishing
645 .as_ref()
646 .and_then(Weak::upgrade)
647 .is_some_and(|active| ptr::eq(active.as_ref(), hook))
648 {
649 *fishing = None;
650 }
651 }
652
653 pub fn tick(&self) {
660 self.advance_tick();
661 self.tick_item_cooldowns();
662 self.tick_attack_strength();
663 self.tick_throttlers();
664 self.check_idle_timeout();
665 self.tick_client_load_timeout();
666 self.tick_sleep_counter();
667 if self.is_sleeping() {
668 let world = self.get_world();
669 if !self.bed_rule_value_allows(world.dimension_type.bed_rule.can_sleep) {
670 self.stop_sleep_in_bed(false, true);
671 } else if !self.can_interact_with_level()
672 || self
673 .sleeping_pos()
674 .is_none_or(|pos| !world.get_block_state(pos).is_bed())
675 {
676 self.stop_sleep_in_bed(true, true);
677 }
678 }
679
680 self.set_no_physics(self.is_spectator());
681 if self.is_spectator() || self.is_passenger() {
682 self.set_on_ground(false);
683 }
684
685 let tick_position = self.position();
686
687 self.movement.lock().reset_for_tick(tick_position);
689 self.set_old_position_to_current();
690 self.reset_vehicle_movement_for_tick();
691
692 self.default_tick();
693 self.detect_equipment_updates();
694 self.ai_step();
695
696 if let Err(error) = self.try_set_position(tick_position) {
698 panic!(
699 "failed to restore player {} tick position after ai_step: {error}",
700 self.id()
701 );
702 }
703 self.refresh_fluid_contact();
704
705 self.tick_ack_block_changes();
706
707 if !self.has_client_loaded() {
708 }
710
711 self.living_base.decrement_invulnerable_time();
712 self.tick_mob_effects();
713 self.tick_active_item_use();
714 self.tick_stats();
716
717 if self.get_health() <= 0.0 {
718 self.tick_death();
719 } else {
720 let world = self.get_world();
721 self.touch_nearby_items();
722 self.block_breaking.lock().tick(self, &world);
723
724 self.update_player_attributes();
730 self.living_base.refresh_speed_from_attributes();
731 self.tick_regeneration();
732
733 if self.is_sprinting() && !self.food_data.lock().has_enough_food() {
734 self.set_sprinting(false);
735 }
736 }
737
738 if self.disconnect_if_floating_too_long() {
739 return;
740 }
741 if self.disconnect_if_vehicle_floating_too_long() {
742 return;
743 }
744
745 self.tick_living_state();
746
747 self.tick_open_menu();
748 self.flush_inventory_resync();
749 self.broadcast_inventory_changes();
750 self.update_pose();
751
752 {
753 let health = self.get_health();
754 let (food, saturation) = {
755 let food_data = self.food_data.lock();
756 (food_data.food_level, food_data.saturation_level)
757 };
758
759 let saturation_zero = saturation == 0.0;
760
761 let mut sync = self.health_sync.lock();
762 if sync.needs_update(health, food, saturation_zero) {
763 self.send_packet(CSetHealth {
764 health,
765 food,
766 food_saturation: saturation,
767 });
768 sync.record_sent(health, food, saturation_zero);
769 }
770 }
771
772 self.send_experience_packet_if_dirty();
773
774 self.connection.tick();
775 }
776
777 fn send_experience_packet_if_dirty(&self) {
778 let experience_packet = {
779 let mut experience = self.experience.lock();
780 if experience.dirty {
781 experience.dirty = false;
782 Some(CSetExperience {
783 progress: experience.progress(),
784 level: experience.level(),
785 total_experience: experience.total_points(),
786 })
787 } else {
788 None
789 }
790 };
791 if let Some(packet) = experience_packet {
792 self.send_packet(packet);
793 }
794 }
795
796 fn tick_death(&self) {
799 let death_time = self.living_base.increment_death_time();
800
801 if death_time >= DEATH_DURATION && !self.is_removed() {
802 let world = self.get_world();
803 let chunk_pos = *self.last_chunk_pos.lock();
804 world.broadcast_to_nearby(
805 chunk_pos,
806 CEntityEvent {
807 entity_id: self.id(),
808 event: EntityStatus::Poof,
809 },
810 None,
811 );
812
813 world.unregister_player_entity(self);
814 world.chunk_map.remove_player(self);
815 world.entity_tracker().on_player_leave(self);
816 world.player_area_map.remove_by_entity_id(self.id());
817 self.set_removed(RemovalReason::Killed);
818 assert_eq!(
819 self.remove_all_menus_with_disposition(MenuItemDisposition::Drop),
820 MenuRemovalStatus::Complete,
821 "death removal menu cleanup must run outside a menu callback"
822 );
823 }
824 }
825
826 fn tick_stats(&self) {
828 let mut stats = self.stats.lock();
832 stats.increment(
833 vanilla_stat_types::CUSTOM.get(&vanilla_custom_stats::PLAY_TIME),
834 1,
835 );
836 stats.increment(
837 vanilla_stat_types::CUSTOM.get(&vanilla_custom_stats::TOTAL_WORLD_TIME),
838 1,
839 );
840
841 if Entity::is_alive(self) {
842 stats.increment(
843 vanilla_stat_types::CUSTOM.get(&vanilla_custom_stats::TIME_SINCE_DEATH),
844 1,
845 );
846 }
847 if self.is_discrete() {
848 stats.increment(
849 vanilla_stat_types::CUSTOM.get(&vanilla_custom_stats::SNEAK_TIME),
850 1,
851 );
852 }
853 if !self.is_sleeping() {
854 stats.increment(
855 vanilla_stat_types::CUSTOM.get(&vanilla_custom_stats::TIME_SINCE_REST),
856 1,
857 );
858 }
859 }
860
861 fn sync_entity_data(&self) {
863 if let Some(dirty_values) = self.entity_data.lock().pack_dirty() {
864 let packet = CSetEntityData::new(self.id(), dirty_values);
865 self.get_world()
866 .broadcast_to_entity_trackers(self.id(), packet.clone(), None);
867 self.send_packet(packet);
868 }
869 }
870
871 fn update_dirty_mob_effect_entity_data(&self) {
872 if !self.living_base.take_effects_dirty() {
873 return;
874 }
875
876 let mut display = self.living_base.mob_effect_display_state();
877 if self.game_mode() == GameType::Spectator {
878 display.particles = ParticleList::default();
879 display.invisible = true;
880 }
881
882 {
883 let mut entity_data = self.entity_data.lock();
884 let living = entity_data.living_entity_mut();
885 living.effect_particles.set(display.particles);
886 living.effect_ambience.set(display.ambient);
887 }
888
889 self.entity_data.set_base_invisible_flag(display.invisible);
890 self.entity_data
891 .set_base_glowing_flag(self.has_glowing_tag() || display.glowing);
892 }
893
894 #[expect(clippy::unused_self, reason = "this is an api function")]
896 pub fn handle_custom_payload(&self, _packet: SCustomPayload) {}
897
898 pub fn handle_client_tick_end(&self) {
900 self.movement.lock().finish_client_tick();
901 }
902
903 pub fn hurt(&self, world: &World, source: &DamageSource, amount: f32) -> bool {
908 if LivingEntity::is_invulnerable_to(self, world, source) {
909 return false;
910 }
911
912 {
913 let abilities = self.abilities.lock();
914 if abilities.invulnerable && !source.bypasses_invulnerability() {
915 return false;
916 }
917 }
918
919 if self.get_health() <= 0.0 {
921 return false;
922 }
923
924 let mut amount = amount;
926 let causing_entity = source
927 .causing_entity_id
928 .and_then(|entity_id| world.get_entity_by_id(entity_id));
929 if source.scales_with_difficulty(causing_entity.as_deref()) {
930 let difficulty = world.level_data.read().data().difficulty;
931 match difficulty {
932 Difficulty::Peaceful => {
933 amount = 0.0;
934 }
935 Difficulty::Easy => {
936 amount = (amount / 2.0 + 1.0).min(amount);
937 }
938 Difficulty::Hard => {
939 amount = amount * 3.0 / 2.0;
940 }
941 Difficulty::Normal => {}
942 }
943 }
944
945 if amount == 0.0 {
946 return false;
947 }
948
949 LivingEntity::hurt_server(self, world, source, amount)
950 }
951
952 fn disabled_damage_game_rule(source: &DamageSource) -> Option<GameRuleRef<bool>> {
953 if source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_DROWNING) {
954 Some(&DROWNING_DAMAGE)
955 } else if source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FALL) {
956 Some(&FALL_DAMAGE)
957 } else if source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FIRE) {
958 Some(&FIRE_DAMAGE)
959 } else if source.is(&vanilla_damage_type_tags::DamageTypeTag::IS_FREEZING) {
960 Some(&FREEZE_DAMAGE)
961 } else {
962 None
963 }
964 }
965
966 fn actually_hurt(&self, world: &World, source: &DamageSource, amount: f32) {
968 if LivingEntity::is_invulnerable_to(self, world, source) {
969 return;
970 }
971
972 let damage = LivingEntity::get_damage_after_armor_absorb(self, source, amount);
973 let damage = LivingEntity::get_damage_after_magic_absorb(self, source, damage);
974 let original_damage = damage;
975 let damage = (damage - self.get_absorption_amount()).max(0.0);
976 self.set_absorption_amount(self.get_absorption_amount() - (original_damage - damage));
977
978 let absorbed_damage = original_damage - damage;
979 if (0.0..f32::MAX).contains(&absorbed_damage) {
980 self.award_custom_stat_with_count(
981 &vanilla_custom_stats::DAMAGE_ABSORBED,
982 (absorbed_damage * 10.0).round() as i32,
983 );
984 }
985
986 if damage != 0.0 {
988 self.cause_food_exhaustion(source.damage_type.exhaustion);
989 self.set_health(self.get_health() - damage);
990 if damage < f32::MAX {
991 self.award_custom_stat_with_count(
992 &vanilla_custom_stats::DAMAGE_TAKEN,
993 (damage * 10.0).round() as i32,
994 );
995 }
996 self.game_event(&vanilla_game_events::ENTITY_DAMAGE);
997 }
998 }
999
1000 fn die(&self, source: &DamageSource) {
1002 if self.is_removed() {
1003 return;
1004 }
1005 if !self.living_base.mark_death_processed() {
1006 return;
1007 }
1008
1009 self.game_event(&vanilla_game_events::ENTITY_DIE);
1010
1011 self.sync_entity_data();
1012
1013 let world = self.get_world();
1018
1019 let chunk_pos = *self.last_chunk_pos.lock();
1021 world.broadcast_to_nearby(
1022 chunk_pos,
1023 CEntityEvent {
1024 entity_id: self.id(),
1025 event: EntityStatus::Death,
1026 },
1027 None,
1028 );
1029
1030 let show_death_messages = world.get_game_rule(&SHOW_DEATH_MESSAGES);
1031
1032 let death_key = format!("death.attack.{}", source.damage_type.message_id);
1034 let death_message = TranslatedMessage {
1035 key: death_key.into(),
1036 fallback: None,
1037 args: Some(Box::new([TextComponent::plain(
1038 self.gameprofile.name.clone(),
1039 )])),
1040 }
1041 .component();
1042
1043 self.send_packet(CPlayerCombatKill {
1044 player_id: self.id(),
1045 message: if show_death_messages {
1046 death_message.clone()
1047 } else {
1048 TextComponent::const_plain("")
1049 },
1050 });
1051
1052 if show_death_messages {
1054 world.broadcast_system_chat(CSystemChat {
1055 content: death_message,
1056 overlay: false,
1057 });
1058 }
1059
1060 if !world.get_game_rule(&KEEP_INVENTORY) && self.game_mode() != GameType::Spectator {
1061 let drops = self.inventory.lock().take_death_drops();
1062 for item in drops {
1063 let _ = self.drop_item(item, true, false);
1064 }
1065
1066 let reward = self.experience.lock().death_xp_reward();
1067 if reward > 0 {
1068 ExperienceOrbEntity::award(&world, self.position(), reward);
1069 }
1070 }
1071
1072 if let Some(killer) = get_kill_credit(self, world.as_ref()) {
1074 self.award_stat(&vanilla_stat_types::ENTITY_KILLED_BY, killer.entity_type());
1075 killer.award_kill_score(self, source);
1076 }
1078
1079 self.award_custom_stat(&vanilla_custom_stats::DEATHS);
1080 self.reset_custom_stat(&vanilla_custom_stats::TIME_SINCE_DEATH);
1081 self.reset_custom_stat(&vanilla_custom_stats::TIME_SINCE_REST);
1082
1083 self.clear_fire();
1084 self.set_ticks_frozen(0);
1085
1086 if world.get_game_rule(&IMMEDIATE_RESPAWN) {
1087 self.respawn();
1088 }
1089 }
1090
1091 pub fn can_eat(&self, can_always_eat: bool) -> bool {
1093 let invulnerable = { self.abilities.lock().invulnerable };
1094 let needs_foods = { self.food_data.lock().needs_food() };
1095 invulnerable || can_always_eat || needs_foods
1096 }
1097
1098 #[expect(clippy::unused_self, reason = "this is an api function")]
1100 pub const fn cleanup(&self) {}
1101
1102 pub fn get_world(&self) -> Arc<World> {
1104 self.world.load_full()
1105 }
1106
1107 pub(crate) fn server(&self) -> Arc<Server> {
1109 self.server
1110 .upgrade()
1111 .expect("player must not outlive server")
1112 }
1113
1114 pub(crate) fn domain_residence_token(&self) -> DomainResidenceToken {
1116 self.residence.lock().token
1117 }
1118
1119 pub(crate) fn advance_domain_residence(&self) -> DomainResidenceToken {
1121 self.residence.lock().advance()
1122 }
1123
1124 pub(crate) fn is_domain_residence_current(&self, token: DomainResidenceToken) -> bool {
1126 self.residence.lock().token == token
1127 }
1128
1129 pub(crate) fn install_pending_domain_restores(
1131 &self,
1132 token: DomainResidenceToken,
1133 world: &World,
1134 root_vehicle: Option<PersistentRootVehicle>,
1135 ender_pearls: Vec<PersistentEnderPearl>,
1136 ) -> bool {
1137 let mut residence = self.residence.lock();
1138 if residence.token != token {
1139 return false;
1140 }
1141
1142 residence.pending_root_vehicle =
1143 root_vehicle.map(|root_vehicle| PendingRootVehicleRestore {
1144 world: world.key.clone(),
1145 root_vehicle,
1146 });
1147 residence.pending_ender_pearls = ender_pearls;
1148 true
1149 }
1150
1151 pub(crate) fn pending_root_vehicle_for_current_world(&self) -> Option<PersistentRootVehicle> {
1152 let world_key = self.get_world().key.clone();
1153 self.residence
1154 .lock()
1155 .pending_root_vehicle
1156 .as_ref()
1157 .filter(|pending| pending.world == world_key)
1158 .map(|pending| pending.root_vehicle.clone())
1159 }
1160
1161 pub(crate) fn take_matching_pending_root_vehicle(
1162 &self,
1163 token: DomainResidenceToken,
1164 world: &World,
1165 attach: [u8; 16],
1166 root_uuid: [u8; 16],
1167 ) -> Option<PersistentRootVehicle> {
1168 let mut residence = self.residence.lock();
1169 if residence.token != token {
1170 return None;
1171 }
1172 let matches = residence
1173 .pending_root_vehicle
1174 .as_ref()
1175 .is_some_and(|pending| {
1176 pending.world == world.key
1177 && pending.root_vehicle.attach == attach
1178 && pending.root_vehicle.entity.uuid == root_uuid
1179 });
1180 if matches {
1181 residence
1182 .pending_root_vehicle
1183 .take()
1184 .map(|pending| pending.root_vehicle)
1185 } else {
1186 None
1187 }
1188 }
1189
1190 pub(crate) fn pending_ender_pearls(&self) -> Vec<PersistentEnderPearl> {
1191 self.residence.lock().pending_ender_pearls.clone()
1192 }
1193
1194 pub(crate) fn remove_pending_ender_pearl(&self, uuid: Uuid) {
1195 self.residence
1196 .lock()
1197 .pending_ender_pearls
1198 .retain(|pearl| Uuid::from_bytes(pearl.entity.uuid) != uuid);
1199 }
1200
1201 pub(crate) fn discard_pending_ender_pearl(
1202 &self,
1203 token: DomainResidenceToken,
1204 uuid: Uuid,
1205 ) -> bool {
1206 let mut residence = self.residence.lock();
1207 if residence.token != token {
1208 return false;
1209 }
1210 let old_len = residence.pending_ender_pearls.len();
1211 residence
1212 .pending_ender_pearls
1213 .retain(|pearl| Uuid::from_bytes(pearl.entity.uuid) != uuid);
1214 residence.pending_ender_pearls.len() != old_len
1215 }
1216
1217 pub(crate) fn take_matching_pending_ender_pearl(
1218 &self,
1219 token: DomainResidenceToken,
1220 world: &World,
1221 uuid: Uuid,
1222 ) -> Option<PersistentEnderPearl> {
1223 let mut residence = self.residence.lock();
1224 if residence.token != token {
1225 return None;
1226 }
1227 let world_key = world.key.to_string();
1228 let index = residence.pending_ender_pearls.iter().position(|pearl| {
1229 pearl.world == world_key && Uuid::from_bytes(pearl.entity.uuid) == uuid
1230 })?;
1231 Some(residence.pending_ender_pearls.remove(index))
1232 }
1233
1234 pub fn register_ender_pearl(&self, pearl: &SharedEntity) {
1237 let uuid = pearl.uuid();
1238 let mut pearls = self.ender_pearls.lock();
1239 pearls.retain(|weak| {
1240 weak.upgrade()
1241 .is_some_and(|p| !p.is_removed() && p.uuid() != uuid)
1242 });
1243 pearls.push(Arc::downgrade(pearl));
1244 drop(pearls);
1245 self.remove_pending_ender_pearl(uuid);
1246 }
1247
1248 pub fn deregister_ender_pearl(&self, uuid: Uuid) {
1251 self.ender_pearls
1252 .lock()
1253 .retain(|weak| weak.upgrade().is_some_and(|p| p.uuid() != uuid));
1254 }
1255
1256 #[must_use]
1258 pub fn ender_pearls(&self) -> Vec<SharedEntity> {
1259 let mut pearls = self.ender_pearls.lock();
1260 pearls.retain(|weak| weak.upgrade().is_some_and(|p| !p.is_removed()));
1261 pearls.iter().filter_map(Weak::upgrade).collect()
1262 }
1263
1264 pub(crate) fn rebind_ender_pearls_to(&self, replacement: &Arc<Self>) {
1266 debug_assert_eq!(self.gameprofile.id, replacement.gameprofile.id);
1267 let replacement_entity: SharedEntity = replacement.clone();
1268 for pearl in self.ender_pearls() {
1269 if pearl.projectile_owner_uuid() == Some(self.gameprofile.id) {
1270 pearl.restore_owner_reference(&replacement_entity);
1271 }
1272 }
1273 }
1274
1275 pub(crate) fn save_command_nbt(&self, nbt: &mut NbtCompound) {
1277 {
1278 let inventory = self.inventory.lock();
1279 nbt.insert("Inventory", inventory.to_vanilla_inventory_nbt());
1280 nbt.insert("SelectedItemSlot", i32::from(inventory.get_selected_slot()));
1281 }
1282
1283 {
1284 let experience = self.experience.lock();
1285 nbt.insert("XpP", experience.progress());
1286 nbt.insert("XpLevel", experience.level());
1287 nbt.insert("XpTotal", experience.total_points());
1288 }
1289 nbt.insert("Score", self.score());
1290
1291 {
1292 let food = self.food_data.lock();
1293 nbt.insert("foodLevel", food.food_level);
1294 nbt.insert("foodTickTimer", food.tick_timer);
1295 nbt.insert("foodSaturationLevel", food.saturation_level);
1296 nbt.insert("foodExhaustionLevel", food.exhaustion_level);
1297 }
1298
1299 {
1300 let abilities = self.abilities.lock();
1301 let mut abilities_nbt = NbtCompound::new();
1302 abilities_nbt.insert(
1303 "invulnerable",
1304 NbtTag::Byte(i8::from(abilities.invulnerable)),
1305 );
1306 abilities_nbt.insert("flying", NbtTag::Byte(i8::from(abilities.flying)));
1307 abilities_nbt.insert("mayfly", NbtTag::Byte(i8::from(abilities.may_fly)));
1308 abilities_nbt.insert("instabuild", NbtTag::Byte(i8::from(abilities.instabuild)));
1309 abilities_nbt.insert("mayBuild", NbtTag::Byte(i8::from(abilities.may_build)));
1310 abilities_nbt.insert("flySpeed", abilities.flying_speed);
1311 abilities_nbt.insert("walkSpeed", abilities.walking_speed);
1312 nbt.insert("abilities", NbtTag::Compound(abilities_nbt));
1313 }
1314
1315 nbt.insert("playerGameType", self.game_mode() as i32);
1316 if let Some(previous_game_mode) = self.previous_game_mode() {
1317 nbt.insert("previousPlayerGameType", previous_game_mode as i32);
1318 }
1319 nbt.insert(
1320 "seenCredits",
1321 NbtTag::Byte(i8::from(self.has_seen_credits())),
1322 );
1323 nbt.insert("Dimension", self.get_world().key.to_string());
1324
1325 if let Some(vehicle) = self.vehicle()
1326 && let Some(root_vehicle) = self.root_vehicle()
1327 && root_vehicle.id() != self.id()
1328 && root_vehicle.has_exactly_one_player_passenger()
1329 && let Some(entity_nbt) = root_vehicle.nbt_for_passenger_save()
1330 {
1331 let mut root_vehicle_nbt = NbtCompound::new();
1332 root_vehicle_nbt.insert(
1333 "Attach",
1334 NbtTag::IntArray(vehicle.uuid().to_int_array().to_vec()),
1335 );
1336 root_vehicle_nbt.insert("Entity", NbtTag::Compound(entity_nbt));
1337 nbt.insert("RootVehicle", NbtTag::Compound(root_vehicle_nbt));
1338 }
1339
1340 let ender_pearls = self
1341 .ender_pearls()
1342 .into_iter()
1343 .filter_map(|pearl| {
1344 let world = pearl.level()?;
1345 let mut pearl_nbt = pearl.nbt_for_passenger_save()?;
1346 pearl_nbt.insert("ender_pearl_dimension", world.key.to_string());
1347 Some(pearl_nbt)
1348 })
1349 .collect::<Vec<_>>();
1350 if !ender_pearls.is_empty() {
1351 nbt.insert("ender_pearls", NbtList::Compound(ender_pearls));
1352 }
1353 }
1354
1355 pub fn store_ender_pearls_with_player(&self) {
1358 for pearl in self.ender_pearls() {
1359 let world = pearl.level();
1360 let chunk = ChunkPos::from_entity_pos(pearl.position());
1361 pearl.set_removed(RemovalReason::StoredWithPlayer);
1362 if let Some(world) = world {
1363 world.mark_chunk_dirty(chunk);
1364 }
1365 }
1366 }
1367
1368 #[must_use]
1370 pub fn tick_count(&self) -> i32 {
1371 self.tick_state.lock().tick_count()
1372 }
1373
1374 #[must_use]
1376 pub(crate) fn take_xp_delay(&self) -> i32 {
1377 self.tick_state.lock().take_xp_delay()
1378 }
1379
1380 pub(crate) fn set_take_xp_delay(&self, delay: i32) {
1382 self.tick_state.lock().set_take_xp_delay(delay);
1383 }
1384
1385 fn primary_step_sound_block_pos(&self, affecting_pos: BlockPos) -> BlockPos {
1386 let above_pos = affecting_pos.above();
1387 let above_state = self.get_world().get_block_state(above_pos);
1388 let above_block = above_state.get_block();
1389
1390 if above_block.has_tag(&BlockTag::INSIDE_STEP_SOUND_BLOCKS)
1391 || above_block.has_tag(&BlockTag::COMBINATION_STEP_SOUND_BLOCKS)
1392 {
1393 above_pos
1394 } else {
1395 affecting_pos
1396 }
1397 }
1398
1399 pub fn reset_last_action_time(&self) {
1401 *self.last_action_time.lock() = Instant::now();
1402 }
1403
1404 fn check_idle_timeout(&self) {
1405 if let Some(server) = self.server.upgrade() {
1406 let player_idle_timeout = server.player_idle_timeout.load(Ordering::Relaxed);
1407 if player_idle_timeout > 0
1408 && Instant::now().duration_since(*self.last_action_time.lock())
1409 > Duration::from_mins(player_idle_timeout as u64)
1410 && !self.has_won_game()
1411 {
1412 self.disconnect(translations::MULTIPLAYER_DISCONNECT_IDLING.msg());
1413 }
1414 }
1415 }
1416}
1417
1418impl Entity for Player {
1419 fn base(&self) -> &EntityBase {
1420 &self.base
1421 }
1422
1423 fn entity_type(&self) -> EntityTypeRef {
1424 &vanilla_entities::PLAYER
1425 }
1426
1427 fn base_tick(&self) {
1428 LivingEntity::base_tick_living_entity(self);
1429 }
1430
1431 fn scoreboard_name(&self) -> String {
1432 self.gameprofile.name.clone()
1433 }
1434
1435 fn name(&self) -> TextComponent {
1436 TextComponent::plain(self.gameprofile.name.clone())
1437 }
1438
1439 fn display_name(&self) -> TextComponent {
1440 self.name()
1441 .click_event(ClickEvent::suggest_command(format!(
1442 "/tell {} ",
1443 self.gameprofile.name
1444 )))
1445 .hover_event(HoverEvent::show_entity(
1446 "minecraft:player",
1447 self.uuid(),
1448 Some(self.name()),
1449 ))
1450 .insertion(self.gameprofile.name.clone())
1451 }
1452
1453 fn plain_text_name(&self) -> String {
1454 self.gameprofile.name.clone()
1455 }
1456
1457 fn look_at(&self, from_anchor: EntityAnchor, target: DVec3) {
1458 apply_entity_look_at(self, from_anchor, target);
1459 self.send_packet(CPlayerLookAt::position(
1460 protocol_look_at_anchor(from_anchor),
1461 target,
1462 ));
1463 }
1464
1465 fn look_at_entity(
1466 &self,
1467 from_anchor: EntityAnchor,
1468 target: &dyn Entity,
1469 target_anchor: EntityAnchor,
1470 ) {
1471 let target_position = target_anchor.position(target);
1472 apply_entity_look_at(self, from_anchor, target_position);
1473 self.send_packet(CPlayerLookAt::entity(
1474 protocol_look_at_anchor(from_anchor),
1475 target_position,
1476 target.id(),
1477 protocol_look_at_anchor(target_anchor),
1478 ));
1479 }
1480
1481 fn is_always_ticking(&self) -> bool {
1482 true
1483 }
1484
1485 fn update_swimming(&self) {
1486 if self.is_flying() {
1487 self.set_shared_swimming(false);
1488 } else {
1489 self.default_update_swimming();
1490 }
1491 }
1492
1493 fn ride_tick(&self) {
1494 let pre = self.position();
1495 if self.wants_to_stop_riding() && self.is_passenger() {
1496 self.stop_riding();
1497 } else {
1498 self.default_ride_tick();
1499 self.reset_fall_distance();
1500 }
1501 self.check_riding_statistics(self.position() - pre);
1502 }
1503
1504 fn stop_riding(&self) {
1505 let old_vehicle = self.vehicle();
1506 self.base().stop_riding();
1507 self.base.set_boarding_cooldown(0);
1508 let Some(old_vehicle) = old_vehicle else {
1509 return;
1510 };
1511
1512 self.remove_active_effects_for_vehicle(old_vehicle.as_ref());
1513 self.send_packet(CSetPassengers::new(
1514 old_vehicle.id(),
1515 Self::passenger_ids_for_packet(old_vehicle.as_ref()),
1516 ));
1517 }
1518
1519 fn teleport_to(&self, pos: DVec3) -> Result<(), EntityMoveError> {
1520 let (yaw, pitch) = self.rotation();
1521 self.teleport(pos, yaw, pitch)
1522 }
1523
1524 fn start_riding(&self, entity_to_ride: &SharedEntity) -> bool {
1525 let Some(world) = self.level() else {
1526 return false;
1527 };
1528 let Some(passenger) = world.get_entity_by_id(self.id()) else {
1529 return false;
1530 };
1531 if !start_riding_entities(&passenger, entity_to_ride) {
1532 return false;
1533 }
1534
1535 entity_to_ride.position_rider(self.as_entity_event_source());
1536 let position = self.position();
1537 let (yaw, pitch) = self.rotation();
1538 if let Err(error) = self.teleport(position, yaw, pitch) {
1539 panic!(
1540 "failed to synchronize player {} mounted position: {error}",
1541 self.id()
1542 );
1543 }
1544 self.send_active_effects_for_vehicle(entity_to_ride.as_ref());
1545 self.send_packet(CSetPassengers::new(
1546 entity_to_ride.id(),
1547 Self::passenger_ids_for_packet(entity_to_ride.as_ref()),
1548 ));
1549 true
1550 }
1551
1552 fn broadcast_to_player(&self, player: &Player) -> bool {
1553 if player.is_spectator() {
1554 true
1555 } else {
1556 !self.is_spectator()
1557 }
1558 }
1559
1560 fn fall_sounds(&self) -> (SoundEventRef, SoundEventRef) {
1561 (
1562 &sound_events::ENTITY_PLAYER_SMALL_FALL,
1563 &sound_events::ENTITY_PLAYER_BIG_FALL,
1564 )
1565 }
1566
1567 fn is_alive(&self) -> bool {
1568 !self.is_removed() && self.get_health() > 0.0
1569 }
1570
1571 fn blocks_building(&self) -> bool {
1572 true
1573 }
1574
1575 fn is_pickable(&self) -> bool {
1576 !self.is_spectator() && !self.is_removed()
1577 }
1578
1579 fn is_pushable(&self) -> bool {
1580 self.get_health() > 0.0 && !self.is_spectator() && !self.on_climbable()
1581 }
1582
1583 fn on_climbable(&self) -> bool {
1584 Player::on_climbable(self)
1585 }
1586
1587 fn is_spectator(&self) -> bool {
1588 self.game_mode() == GameType::Spectator
1589 }
1590
1591 fn is_flying_player(&self) -> bool {
1592 self.is_flying()
1593 }
1594
1595 fn fire_immune_ticks(&self) -> i32 {
1596 20
1597 }
1598
1599 fn remaining_fire_ticks_cap(&self) -> Option<i32> {
1600 self.abilities.lock().invulnerable.then_some(1)
1601 }
1602
1603 fn get_default_gravity(&self) -> f64 {
1604 LivingEntity::get_attribute_gravity(self)
1605 }
1606
1607 fn fire_ignite_extra_ticks(&self) -> i32 {
1608 rand::random_range(1..=2)
1609 }
1610
1611 fn can_freeze(&self) -> bool {
1612 if self.is_spectator() {
1613 return false;
1614 }
1615
1616 self.default_living_can_freeze()
1617 }
1618
1619 fn make_stuck_in_block(&self, state: BlockStateId, speed_multiplier: DVec3) {
1620 if !self.is_flying() {
1621 self.default_make_stuck_in_block(state, speed_multiplier);
1622 }
1623
1624 }
1626
1627 fn can_be_hit_by_projectile(&self) -> bool {
1628 self.get_health() > 0.0 && self.is_pickable()
1629 }
1630
1631 fn uses_client_movement_packets(&self) -> bool {
1632 true
1633 }
1634
1635 fn can_simulate_movement(&self) -> bool {
1636 true
1637 }
1638
1639 fn is_effective_ai(&self) -> bool {
1640 true
1641 }
1642
1643 fn known_movement(&self) -> DVec3 {
1644 if let Some(vehicle) = self.vehicle()
1645 && vehicle
1646 .controlling_passenger()
1647 .is_none_or(|controller| controller.id() != self.id())
1648 {
1649 return vehicle.known_movement();
1650 }
1651
1652 self.movement.lock().last_known_client_movement()
1653 }
1654
1655 fn known_speed(&self) -> DVec3 {
1656 if let Some(vehicle) = self.vehicle()
1657 && vehicle
1658 .controlling_passenger()
1659 .is_none_or(|controller| controller.id() != self.id())
1660 {
1661 return vehicle.known_speed();
1662 }
1663
1664 self.movement.lock().last_known_client_movement()
1665 }
1666
1667 fn is_suppressing_bounce(&self) -> bool {
1668 self.is_crouching()
1669 }
1670
1671 fn cause_fall_damage(
1672 &self,
1673 fall_distance: f64,
1674 damage_modifier: f32,
1675 source: &DamageSource,
1676 ) -> bool {
1677 if self.abilities.lock().may_fly {
1678 return false;
1679 }
1680
1681 if fall_distance >= 2.0 {
1682 self.award_custom_stat_with_count(
1683 &vanilla_custom_stats::FALL_ONE_CM,
1684 (fall_distance * 100.0).round() as i32,
1685 );
1686 }
1687
1688 LivingEntity::cause_living_fall_damage(self, fall_distance, damage_modifier, source)
1689 }
1690
1691 fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
1692 Some(&self.entity_data)
1693 }
1694
1695 fn update_data_before_sync(&self) {
1696 self.update_dirty_mob_effect_entity_data();
1697 }
1698
1699 fn max_up_step(&self) -> f32 {
1700 self.attributes()
1701 .lock()
1702 .get_value(vanilla_attributes::STEP_HEIGHT)
1703 .unwrap_or(0.6) as f32
1704 }
1705
1706 fn backs_off_from_edge(&self) -> bool {
1707 self.is_crouching() && !self.is_flying()
1708 }
1709
1710 fn is_pushed_by_fluid(&self) -> bool {
1711 !self.is_flying()
1712 }
1713
1714 fn is_crouching(&self) -> bool {
1715 Player::is_crouching(self)
1716 }
1717
1718 fn may_interact(&self, world: &World, pos: BlockPos) -> bool {
1719 world.may_interact(self, pos)
1720 }
1721
1722 fn is_swimming(&self) -> bool {
1723 Player::is_swimming(self)
1724 }
1725
1726 fn sound_source(&self) -> SoundSource {
1727 SoundSource::Players
1728 }
1729
1730 fn play_sound(&self, sound: SoundEventRef, volume: f32, pitch: f32) {
1732 if let Some(world) = self.level() {
1733 world.play_sound_at(
1734 sound,
1735 self.sound_source(),
1736 self.position(),
1737 volume,
1738 pitch,
1739 Some(self.id()),
1740 );
1741 }
1742 }
1743
1744 fn swim_sound(&self) -> SoundEventRef {
1745 &sound_events::ENTITY_PLAYER_SWIM
1746 }
1747
1748 fn play_step_sound(&self, on_pos: BlockPos, on_state: BlockStateId) {
1749 if self.is_in_water() {
1750 self.water_swim_sound();
1751 self.play_muffled_step_sound(on_state);
1752 return;
1753 }
1754
1755 let primary_step_sound_pos = self.primary_step_sound_block_pos(on_pos);
1756 if primary_step_sound_pos == on_pos {
1757 self.play_block_step_sound(on_state);
1758 } else {
1759 let primary_state = self.get_world().get_block_state(primary_step_sound_pos);
1760 if primary_state
1761 .get_block()
1762 .has_tag(&BlockTag::COMBINATION_STEP_SOUND_BLOCKS)
1763 {
1764 self.play_combination_step_sounds(primary_state, on_state);
1765 } else {
1766 self.play_block_step_sound(primary_state);
1767 }
1768 }
1769 }
1770
1771 fn movement_emission(&self) -> EntityMovementEmission {
1772 if self.is_flying() || self.on_ground() && self.is_discrete() {
1773 EntityMovementEmission::None
1774 } else {
1775 EntityMovementEmission::All
1776 }
1777 }
1778
1779 fn on_below_world(&self) {
1780 let world = self.get_world();
1781 self.hurt(
1782 &world,
1783 &DamageSource::environment(&vanilla_damage_types::OUT_OF_WORLD),
1784 4.0,
1785 );
1786 }
1787
1788 fn dimensions_for_pose(&self, pose: EntityPose) -> EntityDimensions {
1789 let dimensions = Player::dimensions_for_pose(pose);
1790 if pose == EntityPose::Sleeping || self.entity_type().fixed {
1791 dimensions
1792 } else {
1793 dimensions.scale(LivingEntity::get_scale(self))
1794 }
1795 }
1796
1797 fn hurt(&self, world: &World, source: &DamageSource, amount: f32) -> bool {
1798 Player::hurt(self, world, source, amount)
1801 }
1802
1803 fn killed_entity(
1804 &self,
1805 _world: &World,
1806 entity: &dyn LivingEntity,
1807 _source: &DamageSource,
1808 ) -> bool {
1809 log::debug!("1");
1810 self.award_stat(&vanilla_stat_types::ENTITY_KILLED, entity.entity_type());
1811 true
1812 }
1813
1814 fn award_kill_score(&self, victim: &dyn Entity, _killing_blow: &DamageSource) {
1815 if self.id() != victim.id() {
1816 self.award_custom_stat(if victim.as_player().is_some() {
1819 &vanilla_custom_stats::PLAYER_KILLS
1820 } else {
1821 &vanilla_custom_stats::MOB_KILLS
1822 });
1823 }
1825 }
1826}
1827
1828const fn protocol_look_at_anchor(anchor: EntityAnchor) -> LookAtAnchor {
1829 match anchor {
1830 EntityAnchor::Feet => LookAtAnchor::Feet,
1831 EntityAnchor::Eyes => LookAtAnchor::Eyes,
1832 }
1833}
1834
1835impl LivingEntity for Player {
1836 fn living_synced_data(&self) -> Option<&dyn LivingEntitySyncedData> {
1837 Some(&self.entity_data)
1838 }
1839
1840 fn tick_living_entity(&self) {
1841 Player::tick(self);
1842 }
1843
1844 fn get_health(&self) -> f32 {
1845 *self.entity_data.lock().living_entity().health.get()
1846 }
1847
1848 fn set_health(&self, health: f32) {
1849 let max_health = self.get_max_health();
1850 let clamped = health.clamp(0.0, max_health);
1851 self.entity_data
1852 .lock()
1853 .living_entity_mut()
1854 .health
1855 .set(clamped);
1856 }
1857
1858 fn living_base(&self) -> &LivingEntityBase {
1859 &self.living_base
1860 }
1861
1862 fn is_using_item(&self) -> bool {
1863 self.living_base.is_using_item()
1864 }
1865
1866 fn get_luck(&self) -> f32 {
1867 self.attributes()
1868 .lock()
1869 .required_value(vanilla_attributes::LUCK) as f32
1870 }
1871
1872 fn can_be_seen_as_enemy(&self) -> bool {
1873 !self.abilities.lock().invulnerable
1874 && !self.is_invulnerable()
1875 && self.can_be_seen_by_anyone()
1876 }
1877
1878 fn is_invulnerable_to(&self, world: &World, source: &DamageSource) -> bool {
1879 if self.default_is_invulnerable_to(source)
1880 || enchantment_helper::is_immune_to_damage(world, self, source)
1881 {
1882 return true;
1883 }
1884
1885 if let Some(rule) = Self::disabled_damage_game_rule(source) {
1886 return !world.get_game_rule(rule);
1887 }
1888
1889 !self.has_client_loaded()
1890 }
1891
1892 fn hurt_armor(&self, source: &DamageSource, damage: f32) {
1893 self.do_hurt_equipment(
1894 source,
1895 damage,
1896 &[
1897 EquipmentSlot::Feet,
1898 EquipmentSlot::Legs,
1899 EquipmentSlot::Chest,
1900 EquipmentSlot::Head,
1901 ],
1902 );
1903 }
1904
1905 fn actually_hurt(&self, world: &World, source: &DamageSource, amount: f32) {
1906 Player::actually_hurt(self, world, source, amount);
1907 }
1908
1909 fn hurt_broadcast_chunk(&self) -> ChunkPos {
1910 *self.last_chunk_pos.lock()
1911 }
1912
1913 fn die(&self, source: &DamageSource) {
1914 Player::die(self, source);
1915 }
1916
1917 fn with_equipment_slot(&self, slot: EquipmentSlot, visitor: &mut dyn FnMut(&ItemStack)) {
1918 let inventory = self.inventory.lock();
1919 visitor(inventory.get_ref(slot));
1920 }
1921
1922 fn with_equipment_slot_mut(
1923 &self,
1924 slot: EquipmentSlot,
1925 visitor: &mut dyn FnMut(&mut ItemStack),
1926 ) {
1927 let mut inventory = self.inventory.lock();
1928 inventory.with_equipment_item_mut(slot, visitor);
1929 }
1930
1931 fn interact_living_entity_with_equippable(
1932 &self,
1933 player: &Player,
1934 hand: InteractionHand,
1935 ) -> InteractionResult {
1936 let item_stack = {
1937 let inventory = player.inventory.lock();
1938 let item_stack = inventory.get_item_in_hand(hand);
1939 item_stack.clone()
1940 };
1941 let Some(equippable) = item_stack.get_equippable() else {
1942 return InteractionResult::Pass;
1943 };
1944 if !equippable.equip_on_interact {
1945 return InteractionResult::Pass;
1946 }
1947
1948 let slot = equippable.slot;
1949 let can_equip = |stack: &ItemStack| {
1950 stack.get_equippable().is_some_and(|equippable| {
1951 equippable.equip_on_interact
1952 && equippable.slot == slot
1953 && self.is_equippable_in_slot(stack, slot)
1954 })
1955 };
1956 if !can_equip(&item_stack) || !Entity::is_alive(self) {
1957 return InteractionResult::Pass;
1958 }
1959
1960 let source_ref = ContainerRef::from(player.inventory.clone());
1961 let target_ref = ContainerRef::from(self.inventory.clone());
1962 let source_id = source_ref.container_id();
1963 let target_id = target_ref.container_id();
1964 let mut guard = ContainerLockGuard::lock_all(&[source_ref, target_ref]);
1965 let source_slot = match hand {
1966 InteractionHand::MainHand => EquipmentSlot::MainHand,
1967 InteractionHand::OffHand => EquipmentSlot::OffHand,
1968 };
1969
1970 let equipped = if source_id == target_id {
1971 let Some(inventory) = guard.get_typed_mut::<PlayerInventory>(source_id) else {
1972 unreachable!("player inventory container retains its concrete type");
1973 };
1974 if !can_equip(inventory.get_item_in_hand(hand)) || !inventory.get_ref(slot).is_empty() {
1975 return InteractionResult::Pass;
1976 }
1977
1978 let equipped = inventory.get_mut(source_slot).split(1);
1979 if equipped.is_empty() {
1980 return InteractionResult::Pass;
1981 }
1982 let equipped_for_effects = equipped.copy_with_count(1);
1983 *inventory.get_mut(slot) = equipped;
1984 equipped_for_effects
1985 } else {
1986 let Some((source_inventory, target_inventory)) =
1987 guard.get_two_typed_mut::<PlayerInventory, PlayerInventory>(source_id, target_id)
1988 else {
1989 unreachable!("player inventory containers retain their concrete type");
1990 };
1991 if !can_equip(source_inventory.get_item_in_hand(hand))
1992 || !target_inventory.get_ref(slot).is_empty()
1993 {
1994 return InteractionResult::Pass;
1995 }
1996
1997 let equipped = source_inventory.get_mut(source_slot).split(1);
1998 if equipped.is_empty() {
1999 return InteractionResult::Pass;
2000 }
2001 let equipped_for_effects = equipped.copy_with_count(1);
2002 *target_inventory.get_mut(slot) = equipped;
2003 equipped_for_effects
2004 };
2005 drop(guard);
2006
2007 player.inventory.lock().set_changed();
2008 if source_id != target_id {
2009 self.inventory.lock().set_changed();
2010 }
2011
2012 if let Some(sound) = self.equip_sound(slot, &equipped) {
2013 self.play_sound(sound, 1.0, 1.0);
2014 }
2015 InteractionResult::Success
2017 }
2018
2019 fn has_infinite_materials(&self) -> bool {
2020 Player::has_infinite_materials(self)
2021 }
2022
2023 fn handle_extra_items_created_on_use(&self, extra: ItemStack) {
2024 let leftover = self.inventory.lock().add_or_return(extra);
2025 if !leftover.is_empty() {
2026 let _ = self.drop_item(leftover, false, false);
2027 }
2028 }
2029
2030 fn get_absorption_amount(&self) -> f32 {
2031 *self.entity_data.lock().player_absorption.get()
2032 }
2033
2034 fn set_absorption_amount(&self, amount: f32) {
2035 let max_absorption = self
2036 .living_base
2037 .attributes()
2038 .lock()
2039 .required_value(vanilla_attributes::MAX_ABSORPTION) as f32;
2040 self.entity_data
2041 .lock()
2042 .player_absorption
2043 .set(amount.clamp(0.0, max_absorption));
2044 }
2045
2046 fn is_affected_by_fluids(&self) -> bool {
2047 !self.is_flying()
2048 }
2049
2050 fn can_glide(&self) -> bool {
2051 !self.is_flying() && self.default_can_glide()
2052 }
2053
2054 fn is_immobile(&self) -> bool {
2055 self.default_is_immobile() || self.is_sleeping()
2056 }
2057
2058 fn stop_sleeping(&self) {
2059 self.stop_sleep_in_bed(true, true);
2060 }
2061
2062 fn jump_from_ground(&self) {
2063 self.default_jump_from_ground();
2064 self.award_custom_stat(&vanilla_custom_stats::JUMP);
2065 if self.is_sprinting() {
2066 self.cause_food_exhaustion(food_constants::EXHAUSTION_SPRINT_JUMP);
2067 } else {
2068 self.cause_food_exhaustion(food_constants::EXHAUSTION_JUMP);
2069 }
2070 }
2071
2072 fn ai_step(&self) -> Option<MoveResult> {
2073 if self.is_flying() && !self.is_passenger() {
2074 self.reset_fall_distance();
2075 }
2076
2077 let result = self.default_ai_step();
2078 self.set_y_head_rot(self.rotation().0);
2079 result
2080 }
2081
2082 fn travel(&self, input: DVec3) -> Option<MoveResult> {
2083 if self.is_passenger() {
2084 return self.default_travel(input);
2085 }
2086
2087 if self.is_swimming() {
2088 let look_angle_y = self.look_angle().y;
2089 let multiplier = if look_angle_y < -0.2 { 0.085 } else { 0.06 };
2090 let has_fluid_above = self.level().is_some_and(|world| {
2091 let position = self.position();
2092 let pos = BlockPos::containing(position.x, position.y + 0.9, position.z);
2093 !get_fluid_state(&world, pos).is_empty()
2094 });
2095 if look_angle_y <= 0.0 || self.is_jumping() || has_fluid_above {
2096 let velocity = self.velocity();
2097 self.set_velocity(
2098 velocity + DVec3::new(0.0, (look_angle_y - velocity.y) * multiplier, 0.0),
2099 );
2100 }
2101 }
2102
2103 if self.is_flying() {
2104 let original_movement_y = self.velocity().y;
2105 let result = self.default_travel(input);
2106 let velocity = self.velocity();
2107 self.set_velocity(DVec3::new(
2108 velocity.x,
2109 original_movement_y * 0.6,
2110 velocity.z,
2111 ));
2112 result
2113 } else {
2114 self.default_travel(input)
2115 }
2116 }
2117
2118 fn get_flying_speed(&self) -> f32 {
2119 if self.is_flying() && !self.is_passenger() {
2120 let flying_speed = self.abilities.lock().flying_speed;
2121 if self.is_sprinting() {
2122 flying_speed * 2.0
2123 } else {
2124 flying_speed
2125 }
2126 } else if self.is_sprinting() {
2127 0.025_999_999
2128 } else {
2129 0.02
2130 }
2131 }
2132
2133 fn on_equipped_item_broken(&self, item: ItemRef, slot: EquipmentSlot) {
2134 self.broadcast_entity_event(slot.into());
2135 self.refresh_equipment_attribute_modifiers(slot);
2136 self.award_stat(&vanilla_stat_types::ITEM_BROKEN, item);
2137 }
2138}
2139
2140impl TextResolutor for Player {
2141 fn resolve_content(&self, _resolvable: &Resolvable) -> TextComponent {
2142 TextComponent::new()
2143 }
2144
2145 fn resolve_custom(&self, _data: &CustomData) -> Option<TextComponent> {
2146 None
2147 }
2148
2149 fn translate(&self, _key: &str) -> Option<String> {
2150 None
2151 }
2152}
2153
2154#[cfg(test)]
2155mod tests;