azalea_client/
local_player.rs

1use std::{collections::HashMap, io, sync::Arc};
2
3use azalea_auth::game_profile::GameProfile;
4use azalea_core::game_type::GameMode;
5use azalea_protocol::packets::game::c_player_abilities::ClientboundPlayerAbilities;
6use azalea_world::{Instance, PartialInstance};
7use bevy_ecs::{component::Component, prelude::*};
8use derive_more::{Deref, DerefMut};
9use parking_lot::RwLock;
10use thiserror::Error;
11use tokio::sync::mpsc;
12use tracing::error;
13use uuid::Uuid;
14
15use crate::{ClientInformation, PlayerInfo, events::Event as AzaleaEvent};
16
17/// A component that keeps strong references to our [`PartialInstance`] and
18/// [`Instance`] for local players.
19///
20/// This can also act as a convenience for accessing the player's Instance since
21/// the alternative is to look up the player's [`InstanceName`] in the
22/// [`InstanceContainer`].
23///
24/// [`InstanceContainer`]: azalea_world::InstanceContainer
25/// [`InstanceName`]: azalea_world::InstanceName
26#[derive(Component, Clone)]
27pub struct InstanceHolder {
28    /// The partial instance is the world this client currently has loaded. It
29    /// has a limited render distance.
30    pub partial_instance: Arc<RwLock<PartialInstance>>,
31    /// The world is the combined [`PartialInstance`]s of all clients in the
32    /// same world.
33    ///
34    /// This is only relevant if you're using a shared world (i.e. a
35    /// swarm).
36    pub instance: Arc<RwLock<Instance>>,
37}
38
39/// A component only present in players that contains the [`GameProfile`] (which
40/// you can use to get a player's name).
41///
42/// Note that it's possible for this to be missing in a player if the server
43/// never sent the player info for them (though this is uncommon).
44#[derive(Component, Clone, Debug, Deref, DerefMut)]
45pub struct GameProfileComponent(pub GameProfile);
46
47/// The gamemode of a local player. For a non-local player, you can look up the
48/// player in the [`TabList`].
49#[derive(Component, Clone, Debug, Copy)]
50pub struct LocalGameMode {
51    pub current: GameMode,
52    pub previous: Option<GameMode>,
53}
54
55/// A component that contains the abilities the player has, like flying
56/// or instantly breaking blocks. This is only present on local players.
57#[derive(Clone, Debug, Component, Default)]
58pub struct PlayerAbilities {
59    pub invulnerable: bool,
60    pub flying: bool,
61    pub can_fly: bool,
62    /// Whether the player can instantly break blocks and can duplicate blocks
63    /// in their inventory.
64    pub instant_break: bool,
65
66    pub flying_speed: f32,
67    /// Used for the fov
68    pub walking_speed: f32,
69}
70impl From<&ClientboundPlayerAbilities> for PlayerAbilities {
71    fn from(packet: &ClientboundPlayerAbilities) -> Self {
72        Self {
73            invulnerable: packet.flags.invulnerable,
74            flying: packet.flags.flying,
75            can_fly: packet.flags.can_fly,
76            instant_break: packet.flags.instant_break,
77            flying_speed: packet.flying_speed,
78            walking_speed: packet.walking_speed,
79        }
80    }
81}
82
83/// Level must be 0..=4
84#[derive(Component, Clone, Default, Deref, DerefMut)]
85pub struct PermissionLevel(pub u8);
86
87/// A component that contains a map of player UUIDs to their information in the
88/// tab list.
89///
90/// ```
91/// # use azalea_client::TabList;
92/// # fn example(client: &azalea_client::Client) {
93/// let tab_list = client.component::<TabList>();
94/// println!("Online players:");
95/// for (uuid, player_info) in tab_list.iter() {
96///     println!("- {} ({}ms)", player_info.profile.name, player_info.latency);
97/// }
98/// # }
99/// ```
100///
101/// For convenience, `TabList` is also a resource in the ECS.
102/// It's set to be the same as the tab list for the last client whose tab list
103/// was updated.
104/// This means you should avoid using `TabList` as a resource unless you know
105/// all of your clients will have the same tab list.
106#[derive(Component, Resource, Clone, Debug, Deref, DerefMut, Default)]
107pub struct TabList(HashMap<Uuid, PlayerInfo>);
108
109#[derive(Component, Clone)]
110pub struct Hunger {
111    /// The main hunger bar. Goes from 0 to 20.
112    pub food: u32,
113    /// The amount of saturation the player has. This isn't shown in normal
114    /// vanilla clients but it's a separate counter that makes it so your hunger
115    /// only starts decreasing when this is 0.
116    pub saturation: f32,
117}
118
119impl Default for Hunger {
120    fn default() -> Self {
121        Hunger {
122            food: 20,
123            saturation: 5.,
124        }
125    }
126}
127
128impl InstanceHolder {
129    /// Create a new `InstanceHolder` for the given entity.
130    ///
131    /// The partial instance will be created for you. The render distance will
132    /// be set to a default value, which you can change by creating a new
133    /// partial_instance.
134    pub fn new(entity: Entity, instance: Arc<RwLock<Instance>>) -> Self {
135        let client_information = ClientInformation::default();
136
137        InstanceHolder {
138            instance,
139            partial_instance: Arc::new(RwLock::new(PartialInstance::new(
140                azalea_world::chunk_storage::calculate_chunk_storage_range(
141                    client_information.view_distance.into(),
142                ),
143                Some(entity),
144            ))),
145        }
146    }
147}
148
149#[derive(Error, Debug)]
150pub enum HandlePacketError {
151    #[error("{0}")]
152    Poison(String),
153    #[error(transparent)]
154    Io(#[from] io::Error),
155    #[error(transparent)]
156    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
157    #[error("{0}")]
158    Send(#[from] mpsc::error::SendError<AzaleaEvent>),
159}
160
161impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
162    fn from(e: std::sync::PoisonError<T>) -> Self {
163        HandlePacketError::Poison(e.to_string())
164    }
165}