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